Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qnetworkinterface_unix.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2016 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:significant reason:trusted-data
5
6#include "qbytearray.h"
7#include "qset.h"
11#include "qalgorithms.h"
12
13#include <QtCore/private/qduplicatetracker_p.h>
14
15#ifndef QT_NO_NETWORKINTERFACE
16
17#if QT_CONFIG(getifaddrs)
18# include <ifaddrs.h>
19#endif
20
21#ifdef QT_LINUXBASE
22# include <arpa/inet.h>
23# ifndef SIOCGIFBRDADDR
24# define SIOCGIFBRDADDR 0x8919
25# endif
26#endif // QT_LINUXBASE
27
28#include <qplatformdefs.h>
29
31
32static QHostAddress addressFromSockaddr(sockaddr *sa, int ifindex = 0, const QString &ifname = QString())
33{
34 QHostAddress address;
35 if (!sa)
36 return address;
37
38 if (sa->sa_family == AF_INET)
39 address.setAddress(htonl(((sockaddr_in *)sa)->sin_addr.s_addr));
40 else if (sa->sa_family == AF_INET6) {
41 address.setAddress(((sockaddr_in6 *)sa)->sin6_addr.s6_addr);
42 int scope = ((sockaddr_in6 *)sa)->sin6_scope_id;
43 if (scope && scope == ifindex) {
44 // this is the most likely scenario:
45 // a scope ID in a socket is that of the interface this address came from
46 address.setScopeId(ifname);
47 } else if (scope) {
48 address.setScopeId(QNetworkInterfaceManager::interfaceNameFromIndex(scope));
49 }
50 }
51 return address;
52}
53
54template <typename Req> [[maybe_unused]]
55static auto &ifreq_index(Req &req, std::enable_if_t<sizeof(std::declval<Req>().ifr_index) != 0, int> = 0)
56{
57 return req.ifr_index;
58}
59
60template <typename Req> [[maybe_unused]]
61static auto &ifreq_index(Req &req, std::enable_if_t<sizeof(std::declval<Req>().ifr_ifindex) != 0, int> = 0)
62{
63 return req.ifr_ifindex;
64}
65
66uint QNetworkInterfaceManager::interfaceIndexFromName(const QString &name)
67{
68#if QT_CONFIG(ipv6ifname)
69 return ::if_nametoindex(name.toLatin1().constData());
70#elif defined(SIOCGIFINDEX)
71 struct ifreq req;
72 int socket = qt_safe_socket(AF_INET, SOCK_STREAM, 0);
73 if (socket < 0)
74 return 0;
75
76 const QByteArray name8bit = name.toLatin1();
77 memset(&req, 0, sizeof(ifreq));
78 if (!name8bit.isNull())
79 memcpy(req.ifr_name, name8bit.data(), qMin(size_t(name8bit.length()) + 1, sizeof(req.ifr_name) - 1));
80
81 uint id = 0;
82 if (qt_safe_ioctl(socket, SIOCGIFINDEX, &req) >= 0)
83 id = ifreq_index(req);
84 qt_safe_close(socket);
85 return id;
86#else
87 Q_UNUSED(name);
88 return 0;
89#endif
90}
91
93{
94#if QT_CONFIG(ipv6ifname)
95 char buf[IF_NAMESIZE];
96 if (::if_indextoname(index, buf))
97 return QString::fromLatin1(buf);
98#elif defined(SIOCGIFNAME)
99 struct ifreq req;
100 int socket = qt_safe_socket(AF_INET, SOCK_STREAM, 0);
101 if (socket >= 0) {
102 memset(&req, 0, sizeof(ifreq));
103 ifreq_index(req) = index;
104 if (qt_safe_ioctl(socket, SIOCGIFNAME, &req) >= 0) {
105 qt_safe_close(socket);
106 return QString::fromLatin1(req.ifr_name);
107 }
108 qt_safe_close(socket);
109 }
110#endif
111 return QString::number(uint(index));
112}
113
114static int getMtu(int socket, struct ifreq *req)
115{
116#ifdef SIOCGIFMTU
117 if (qt_safe_ioctl(socket, SIOCGIFMTU, req) == 0)
118 return req->ifr_mtu;
119#endif
120 return 0;
121}
122
123#if !QT_CONFIG(getifaddrs)
124// getifaddrs not available
125
126static QSet<QByteArray> interfaceNames(int socket)
127{
128 QSet<QByteArray> result;
129#if !QT_CONFIG(ipv6ifname)
130 QByteArray storageBuffer;
131 struct ifconf interfaceList;
132 static const int STORAGEBUFFER_GROWTH = 256;
133
134 forever {
135 // grow the storage buffer
136 storageBuffer.resize(storageBuffer.size() + STORAGEBUFFER_GROWTH);
137 interfaceList.ifc_buf = storageBuffer.data();
138 interfaceList.ifc_len = storageBuffer.size();
139
140 // get the interface list
141 if (qt_safe_ioctl(socket, SIOCGIFCONF, &interfaceList) >= 0) {
142 if (int(interfaceList.ifc_len + sizeof(ifreq) + 64) < storageBuffer.size()) {
143 // if the buffer was big enough, break
144 storageBuffer.resize(interfaceList.ifc_len);
145 break;
146 }
147 } else {
148 // internal error
149 return result;
150 }
151 if (storageBuffer.size() > 100000) {
152 // out of space
153 return result;
154 }
155 }
156
157 int interfaceCount = interfaceList.ifc_len / sizeof(ifreq);
158 for (int i = 0; i < interfaceCount; ++i) {
159 QByteArray name = QByteArray(interfaceList.ifc_req[i].ifr_name);
160 if (!name.isEmpty())
161 result << name;
162 }
163
164 return result;
165#else
166 Q_UNUSED(socket);
167
168 // use if_nameindex
169 struct if_nameindex *interfaceList = ::if_nameindex();
170 for (struct if_nameindex *ptr = interfaceList; ptr && ptr->if_name; ++ptr)
171 result << ptr->if_name;
172
173 if_freenameindex(interfaceList);
174 return result;
175#endif
176}
177
178static QNetworkInterfacePrivate *findInterface(int socket, QList<QNetworkInterfacePrivate *> &interfaces,
179 struct ifreq &req)
180{
181 QNetworkInterfacePrivate *iface = nullptr;
182 int ifindex = 0;
183
184#if QT_CONFIG(ipv6ifname) || defined(SIOCGIFINDEX)
185 // Get the interface index
186# ifdef SIOCGIFINDEX
187 if (qt_safe_ioctl(socket, SIOCGIFINDEX, &req) >= 0)
188 ifindex = ifreq_index(req);
189# else
190 ifindex = if_nametoindex(req.ifr_name);
191# endif
192
193 // find the interface data
194 QList<QNetworkInterfacePrivate *>::Iterator if_it = interfaces.begin();
195 for ( ; if_it != interfaces.end(); ++if_it)
196 if ((*if_it)->index == ifindex) {
197 // existing interface
198 iface = *if_it;
199 break;
200 }
201#else
202 Q_UNUSED(socket);
203 // Search by name
204 QList<QNetworkInterfacePrivate *>::Iterator if_it = interfaces.begin();
205 for ( ; if_it != interfaces.end(); ++if_it)
206 if ((*if_it)->name == QLatin1StringView(req.ifr_name)) {
207 // existing interface
208 iface = *if_it;
209 break;
210 }
211#endif
212
213 if (!iface) {
214 // new interface, create data:
215 iface = new QNetworkInterfacePrivate;
216 iface->index = ifindex;
217 interfaces << iface;
218 }
219
220 return iface;
221}
222
223static QList<QNetworkInterfacePrivate *> interfaceListing()
224{
225 QList<QNetworkInterfacePrivate *> interfaces;
226
227 int socket;
228 if ((socket = qt_safe_socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) == -1)
229 return interfaces; // error
230
231 QSet<QByteArray> names = interfaceNames(socket);
232 QSet<QByteArray>::ConstIterator it = names.constBegin();
233 for ( ; it != names.constEnd(); ++it) {
234 ifreq req;
235 memset(&req, 0, sizeof(ifreq));
236 if (!it->isNull())
237 memcpy(req.ifr_name, it->constData(), qMin(size_t(it->length()) + 1, sizeof(req.ifr_name) - 1));
238
239 QNetworkInterfacePrivate *iface = findInterface(socket, interfaces, req);
240
241#ifdef SIOCGIFNAME
242 // Get the canonical name
243 QByteArray oldName = req.ifr_name;
244 if (qt_safe_ioctl(socket, SIOCGIFNAME, &req) >= 0) {
245 iface->name = QString::fromLatin1(req.ifr_name);
246
247 // reset the name:
248 if (!oldName.isNull())
249 memcpy(req.ifr_name, oldName.constData(), qMin(size_t(oldName.length()) + 1, sizeof(req.ifr_name) - 1));
250 } else
251#endif
252 {
253 // use this name anyways
254 iface->name = QString::fromLatin1(req.ifr_name);
255 }
256
257 // Get interface flags
258 if (qt_safe_ioctl(socket, SIOCGIFFLAGS, &req) >= 0) {
259 iface->flags = convertFlags(req.ifr_flags);
260 }
261 iface->mtu = getMtu(socket, &req);
262
263#ifdef SIOCGIFHWADDR
264 // Get the HW address
265 if (qt_safe_ioctl(socket, SIOCGIFHWADDR, &req) >= 0) {
266 uchar *addr = (uchar *)req.ifr_addr.sa_data;
267 iface->hardwareAddress = iface->makeHwAddress(6, addr);
268 }
269#endif
270
271 // Get the address of the interface
272 QNetworkAddressEntry entry;
273 if (qt_safe_ioctl(socket, SIOCGIFADDR, &req) >= 0) {
274 sockaddr *sa = &req.ifr_addr;
275 entry.setIp(addressFromSockaddr(sa));
276
277 // Get the interface broadcast address
278 if (iface->flags & QNetworkInterface::CanBroadcast) {
279 if (qt_safe_ioctl(socket, SIOCGIFBRDADDR, &req) >= 0) {
280 sockaddr *sa = &req.ifr_addr;
281 if (sa->sa_family == AF_INET)
282 entry.setBroadcast(addressFromSockaddr(sa));
283 }
284 }
285
286 // Get the interface netmask
287 if (qt_safe_ioctl(socket, SIOCGIFNETMASK, &req) >= 0) {
288 sockaddr *sa = &req.ifr_addr;
289 entry.setNetmask(addressFromSockaddr(sa));
290 }
291
292 iface->addressEntries << entry;
293 }
294 }
295
296 ::close(socket);
297 return interfaces;
298}
299
300#else
301// use getifaddrs
302
303// platform-specific defs:
304# ifdef Q_OS_LINUX
305QT_BEGIN_INCLUDE_NAMESPACE
306# include <features.h>
307QT_END_INCLUDE_NAMESPACE
308# endif
309
310static int openSocket(int &socket)
311{
312 if (socket == -1)
313 socket = qt_safe_socket(AF_INET, SOCK_DGRAM, 0);
314 return socket;
315}
316
317# if defined(Q_OS_LINUX) && __GLIBC__ - 0 >= 2 && __GLIBC_MINOR__ - 0 >= 1 && !defined(QT_LINUXBASE)
318# include <netpacket/packet.h>
319
320static QList<QNetworkInterfacePrivate *> createInterfaces(ifaddrs *rawList)
321{
322 Q_UNUSED(getMtu);
323 Q_UNUSED(openSocket);
324 QList<QNetworkInterfacePrivate *> interfaces;
325 QDuplicateTracker<QString> seenInterfaces;
326 QDuplicateTracker<int> seenIndexes;
327
328 // On Linux, glibc, uClibc and MUSL obtain the address listing via two
329 // netlink calls: first an RTM_GETLINK to obtain the interface listing,
330 // then one RTM_GETADDR to get all the addresses (uClibc implementation is
331 // copied from glibc; Bionic currently doesn't support getifaddrs). They
332 // synthesize AF_PACKET addresses from the RTM_GETLINK responses, which
333 // means by construction they currently show up first in the interface
334 // listing.
335 for (ifaddrs *ptr = rawList; ptr; ptr = ptr->ifa_next) {
336 if (ptr->ifa_addr && ptr->ifa_addr->sa_family == AF_PACKET) {
337 sockaddr_ll *sll = (sockaddr_ll *)ptr->ifa_addr;
338 QNetworkInterfacePrivate *iface = new QNetworkInterfacePrivate;
339 interfaces << iface;
340 iface->index = sll->sll_ifindex;
341 iface->name = QString::fromLatin1(ptr->ifa_name);
342 iface->flags = convertFlags(ptr->ifa_flags);
343 iface->hardwareAddress = iface->makeHwAddress(sll->sll_halen, (uchar*)sll->sll_addr);
344
345 const bool sawIfaceIndex = seenIndexes.hasSeen(iface->index);
346 Q_ASSERT(!sawIfaceIndex);
347 (void)seenInterfaces.hasSeen(iface->name);
348 }
349 }
350
351 // see if we missed anything:
352 // - virtual interfaces with no HW address have no AF_PACKET
353 // - interface labels have no AF_PACKET, but shouldn't be shown as a new interface
354 for (ifaddrs *ptr = rawList; ptr; ptr = ptr->ifa_next) {
355 if (!ptr->ifa_addr || ptr->ifa_addr->sa_family != AF_PACKET) {
356 QString name = QString::fromLatin1(ptr->ifa_name);
357 if (seenInterfaces.hasSeen(name))
358 continue;
359
360 int ifindex = if_nametoindex(ptr->ifa_name);
361 if (seenIndexes.hasSeen(ifindex))
362 continue;
363
364 QNetworkInterfacePrivate *iface = new QNetworkInterfacePrivate;
365 interfaces << iface;
366 iface->name = name;
367 iface->flags = convertFlags(ptr->ifa_flags);
368 iface->index = ifindex;
369 }
370 }
371
372 return interfaces;
373}
374
375static void getAddressExtraInfo(QNetworkAddressEntry *entry, struct sockaddr *sa, const char *ifname)
376{
377 Q_UNUSED(entry);
378 Q_UNUSED(sa);
379 Q_UNUSED(ifname);
380}
381
382# elif defined(Q_OS_BSD4)
383QT_BEGIN_INCLUDE_NAMESPACE
384# include <net/if_dl.h>
385#if defined(QT_PLATFORM_UIKIT)
386# include "qnetworkinterface_uikit_p.h"
387# include <net/if_types.h>
388#else
389# include <net/if_media.h>
390# include <net/if_types.h>
391# include <netinet/in_var.h>
392#endif // QT_PLATFORM_UIKIT
393QT_END_INCLUDE_NAMESPACE
394
395static QNetworkInterface::InterfaceType probeIfType(int socket, int iftype, struct ifmediareq *req)
396{
397 // Determine the interface type.
398
399 // On Darwin, these are #defines, but on FreeBSD they're just an
400 // enum, so we can't #ifdef them. Use the authoritative list from
401 // https://www.iana.org/assignments/smi-numbers/smi-numbers.xhtml#smi-numbers-5
402 switch (iftype) {
403 case IFT_PPP:
404 return QNetworkInterface::Ppp;
405
406 case IFT_LOOP:
407 return QNetworkInterface::Loopback;
408
409 case IFT_SLIP:
410 return QNetworkInterface::Slip;
411
412 case 0x47: // IFT_IEEE80211
413 return QNetworkInterface::Ieee80211;
414
415 case IFT_IEEE1394:
416 return QNetworkInterface::Ieee1394;
417
418 case IFT_GIF:
419 case IFT_STF:
420 return QNetworkInterface::Virtual;
421 }
422
423 // For the remainder (including Ethernet), let's try SIOGIFMEDIA
424 req->ifm_count = 0;
425 if (qt_safe_ioctl(socket, SIOCGIFMEDIA, req) == 0) {
426 // see https://man.openbsd.org/ifmedia.4
427
428 switch (IFM_TYPE(req->ifm_current)) {
429 case IFM_ETHER:
430 return QNetworkInterface::Ethernet;
431
432#ifdef IFM_FDDI
433 case IFM_FDDI:
434 return QNetworkInterface::Fddi;
435#endif
436
437 case IFM_IEEE80211:
438 return QNetworkInterface::Ieee80211;
439 }
440 }
441
442 return QNetworkInterface::Unknown;
443}
444
445static QList<QNetworkInterfacePrivate *> createInterfaces(ifaddrs *rawList)
446{
447 QList<QNetworkInterfacePrivate *> interfaces;
448 union {
449 struct ifmediareq mediareq;
450 struct ifreq req;
451 };
452 int socket = -1;
453 memset(&mediareq, 0, sizeof(mediareq));
454
455 // ensure both structs start with the name field, of size IFNAMESIZ
456 static_assert(sizeof(mediareq.ifm_name) == sizeof(req.ifr_name));
457 static_assert(offsetof(struct ifmediareq, ifm_name) == 0);
458 static_assert(offsetof(struct ifreq, ifr_name) == 0);
459
460 // on NetBSD we use AF_LINK and sockaddr_dl
461 // scan the list for that family
462 for (ifaddrs *ptr = rawList; ptr; ptr = ptr->ifa_next)
463 if (ptr->ifa_addr && ptr->ifa_addr->sa_family == AF_LINK) {
464 QNetworkInterfacePrivate *iface = new QNetworkInterfacePrivate;
465 interfaces << iface;
466
467 sockaddr_dl *sdl = (sockaddr_dl *)ptr->ifa_addr;
468 iface->index = sdl->sdl_index;
469 iface->name = QString::fromLatin1(ptr->ifa_name);
470 iface->flags = convertFlags(ptr->ifa_flags);
471 iface->hardwareAddress = iface->makeHwAddress(sdl->sdl_alen, (uchar*)LLADDR(sdl));
472
473 qstrncpy(mediareq.ifm_name, ptr->ifa_name, sizeof(mediareq.ifm_name));
474 iface->type = probeIfType(openSocket(socket), sdl->sdl_type, &mediareq);
475 iface->mtu = getMtu(socket, &req);
476 }
477
478 if (socket != -1)
479 qt_safe_close(socket);
480 return interfaces;
481}
482
483static void getAddressExtraInfo(QNetworkAddressEntry *entry, struct sockaddr *sa, const char *ifname)
484{
485 // get IPv6 address lifetimes
486 if (sa->sa_family != AF_INET6)
487 return;
488
489 struct in6_ifreq ifr;
490
491 int s6 = qt_safe_socket(AF_INET6, SOCK_DGRAM, 0);
492 if (Q_UNLIKELY(s6 < 0)) {
493 qErrnoWarning("QNetworkInterface: could not create IPv6 socket");
494 return;
495 }
496
497 qstrncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
498
499 // get flags
500 ifr.ifr_addr = *reinterpret_cast<struct sockaddr_in6 *>(sa);
501 if (qt_safe_ioctl(s6, SIOCGIFAFLAG_IN6, &ifr) < 0) {
502 qt_safe_close(s6);
503 return;
504 }
505 int flags = ifr.ifr_ifru.ifru_flags6;
506 QNetworkInterfacePrivate::calculateDnsEligibility(entry,
507 flags & IN6_IFF_TEMPORARY,
508 flags & IN6_IFF_DEPRECATED);
509
510 // get lifetimes
511 ifr.ifr_addr = *reinterpret_cast<struct sockaddr_in6 *>(sa);
512 if (qt_safe_ioctl(s6, SIOCGIFALIFETIME_IN6, &ifr) < 0) {
513 qt_safe_close(s6);
514 return;
515 }
516 qt_safe_close(s6);
517
518 auto toDeadline = [](time_t when) {
519 QDeadlineTimer deadline = QDeadlineTimer::Forever;
520 if (when) {
521 deadline.setPreciseDeadline(when);
522 }
523 return deadline;
524 };
525 entry->setAddressLifetime(toDeadline(ifr.ifr_ifru.ifru_lifetime.ia6t_preferred),
526 toDeadline(ifr.ifr_ifru.ifru_lifetime.ia6t_expire));
527}
528
529# else // Generic version
530
531static QList<QNetworkInterfacePrivate *> createInterfaces(ifaddrs *rawList)
532{
533 QList<QNetworkInterfacePrivate *> interfaces;
534 int socket = -1;
535
536 // make sure there's one entry for each interface
537 for (ifaddrs *ptr = rawList; ptr; ptr = ptr->ifa_next) {
538 // Get the interface index
539 int ifindex = if_nametoindex(ptr->ifa_name);
540
541 QList<QNetworkInterfacePrivate *>::Iterator if_it = interfaces.begin();
542 for ( ; if_it != interfaces.end(); ++if_it)
543 if ((*if_it)->index == ifindex)
544 // this one has been added already
545 break;
546
547 if (if_it == interfaces.end()) {
548 // none found, create
550 interfaces << iface;
551
552 iface->index = ifindex;
553 iface->name = QString::fromLatin1(ptr->ifa_name);
554 iface->flags = convertFlags(ptr->ifa_flags);
555
556 if ((socket = openSocket(socket)) >= 0) {
557 struct ifreq ifr;
558 qstrncpy(ifr.ifr_name, ptr->ifa_name, sizeof(ifr.ifr_name));
559 iface->mtu = getMtu(socket, &ifr);
560 }
561 }
562 }
563
564 if (socket != -1)
565 qt_safe_close(socket);
566
567 return interfaces;
568}
569
570static void getAddressExtraInfo(QNetworkAddressEntry *entry, struct sockaddr *sa, const char *ifname)
571{
572 Q_UNUSED(entry);
573 Q_UNUSED(sa);
574 Q_UNUSED(ifname);
575}
576# endif
577
578static QList<QNetworkInterfacePrivate *> interfaceListing()
579{
580 QList<QNetworkInterfacePrivate *> interfaces;
581
582 ifaddrs *interfaceListing;
583 if (getifaddrs(&interfaceListing) == -1) {
584 // error
585 return interfaces;
586 }
587
588 interfaces = createInterfaces(interfaceListing);
589 for (ifaddrs *ptr = interfaceListing; ptr; ptr = ptr->ifa_next) {
590 // Find the interface
591 QLatin1StringView name(ptr->ifa_name);
592 QNetworkInterfacePrivate *iface = nullptr;
593 QList<QNetworkInterfacePrivate *>::Iterator if_it = interfaces.begin();
594 for ( ; if_it != interfaces.end(); ++if_it)
595 if ((*if_it)->name == name) {
596 // found this interface already
597 iface = *if_it;
598 break;
599 }
600
601 if (!iface) {
602 // it may be an interface label, search by interface index
603 int ifindex = if_nametoindex(ptr->ifa_name);
604 for (if_it = interfaces.begin(); if_it != interfaces.end(); ++if_it)
605 if ((*if_it)->index == ifindex) {
606 // found this interface already
607 iface = *if_it;
608 break;
609 }
610 }
611
612 if (!iface) {
613 // skip all non-IP interfaces
614 continue;
615 }
616
618 entry.setIp(addressFromSockaddr(ptr->ifa_addr, iface->index, iface->name));
619 if (entry.ip().isNull())
620 // could not parse the address
621 continue;
622
623 entry.setNetmask(addressFromSockaddr(ptr->ifa_netmask, iface->index, iface->name));
624 if (iface->flags & QNetworkInterface::CanBroadcast)
625 entry.setBroadcast(addressFromSockaddr(ptr->ifa_broadaddr, iface->index, iface->name));
626 getAddressExtraInfo(&entry, ptr->ifa_addr, name.latin1());
627
628 iface->addressEntries << entry;
629 }
630
631 freeifaddrs(interfaceListing);
632 return interfaces;
633}
634#endif
635
636QList<QNetworkInterfacePrivate *> QNetworkInterfaceManager::scan()
637{
638 return interfaceListing();
639}
640
641QT_END_NAMESPACE
642
643#endif // QT_NO_NETWORKINTERFACE
The QNetworkAddressEntry class stores one IP address supported by a network interface,...
static uint interfaceIndexFromName(const QString &name)
static QString interfaceNameFromIndex(uint index)
Combined button and popup list for selecting options.
#define AF_INET6
static QT_BEGIN_NAMESPACE QHostAddress addressFromSockaddr(sockaddr *sa, int ifindex=0, const QString &ifname=QString())
static int getMtu(int socket, struct ifreq *req)
static auto & ifreq_index(Req &req, std::enable_if_t< sizeof(std::declval< Req >().ifr_index) !=0, int >=0)