1 | // Copyright (C) 2017 Intel Corporation. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qnetworkinterface.h" |
5 | #include "qnetworkinterface_p.h" |
6 | #include "qnetworkinterface_unix_p.h" |
7 | |
8 | #ifndef QT_NO_NETWORKINTERFACE |
9 | |
10 | #include <qendian.h> |
11 | #include <qobjectdefs.h> |
12 | #include <qvarlengtharray.h> |
13 | |
14 | // according to rtnetlink(7) |
15 | #include <asm/types.h> |
16 | #include <linux/if.h> |
17 | #include <linux/if_arp.h> |
18 | #include <linux/netlink.h> |
19 | #include <linux/rtnetlink.h> |
20 | #include <linux/wireless.h> |
21 | #include <sys/socket.h> |
22 | |
23 | /* in case these aren't defined in linux/if_arp.h (added since 2.6.28) */ |
24 | #define ARPHRD_PHONET 820 /* v2.6.29: PhoNet media type */ |
25 | #define ARPHRD_PHONET_PIPE 821 /* v2.6.29: PhoNet pipe header */ |
26 | #define ARPHRD_IEEE802154 804 /* v2.6.31 */ |
27 | #define ARPHRD_6LOWPAN 825 /* v3.14: IPv6 over LoWPAN */ |
28 | |
29 | QT_BEGIN_NAMESPACE |
30 | |
31 | enum { |
32 | BufferSize = 8192 |
33 | }; |
34 | |
35 | static QNetworkInterface::InterfaceType probeIfType(int socket, struct ifreq *req, short arptype) |
36 | { |
37 | switch (ushort(arptype)) { |
38 | case ARPHRD_LOOPBACK: |
39 | return QNetworkInterface::Loopback; |
40 | |
41 | case ARPHRD_ETHER: |
42 | // check if it's a WiFi interface |
43 | if (qt_safe_ioctl(sockfd: socket, SIOCGIWMODE, arg: req) >= 0) |
44 | return QNetworkInterface::Wifi; |
45 | return QNetworkInterface::Ethernet; |
46 | |
47 | case ARPHRD_SLIP: |
48 | case ARPHRD_CSLIP: |
49 | case ARPHRD_SLIP6: |
50 | case ARPHRD_CSLIP6: |
51 | return QNetworkInterface::Slip; |
52 | |
53 | case ARPHRD_CAN: |
54 | return QNetworkInterface::CanBus; |
55 | |
56 | case ARPHRD_PPP: |
57 | return QNetworkInterface::Ppp; |
58 | |
59 | case ARPHRD_FDDI: |
60 | return QNetworkInterface::Fddi; |
61 | |
62 | case ARPHRD_IEEE80211: |
63 | case ARPHRD_IEEE80211_PRISM: |
64 | case ARPHRD_IEEE80211_RADIOTAP: |
65 | return QNetworkInterface::Ieee80211; |
66 | |
67 | case ARPHRD_IEEE802154: |
68 | return QNetworkInterface::Ieee802154; |
69 | |
70 | case ARPHRD_PHONET: |
71 | case ARPHRD_PHONET_PIPE: |
72 | return QNetworkInterface::Phonet; |
73 | |
74 | case ARPHRD_6LOWPAN: |
75 | return QNetworkInterface::SixLoWPAN; |
76 | |
77 | case ARPHRD_TUNNEL: |
78 | case ARPHRD_TUNNEL6: |
79 | case ARPHRD_NONE: |
80 | case ARPHRD_VOID: |
81 | return QNetworkInterface::Virtual; |
82 | } |
83 | return QNetworkInterface::Unknown; |
84 | } |
85 | |
86 | |
87 | namespace { |
88 | struct NetlinkSocket |
89 | { |
90 | int sock; |
91 | NetlinkSocket(int bufferSize) |
92 | { |
93 | sock = qt_safe_socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); |
94 | if (Q_UNLIKELY(sock == -1)) |
95 | qErrnoWarning(msg: "Could not create AF_NETLINK socket" ); |
96 | |
97 | // set buffer length |
98 | socklen_t len = sizeof(bufferSize); |
99 | setsockopt(fd: sock, SOL_SOCKET, SO_SNDBUF, optval: &bufferSize, optlen: len); |
100 | } |
101 | |
102 | ~NetlinkSocket() |
103 | { |
104 | if (sock != -1) |
105 | qt_safe_close(fd: sock); |
106 | } |
107 | |
108 | operator int() const { return sock; } |
109 | }; |
110 | |
111 | template <typename Lambda> struct ProcessNetlinkRequest |
112 | { |
113 | using FunctionTraits = QtPrivate::FunctionPointer<decltype(&Lambda::operator())>; |
114 | using FirstArgumentPointer = typename FunctionTraits::Arguments::Car; |
115 | using FirstArgument = std::remove_pointer_t<FirstArgumentPointer>; |
116 | static_assert(std::is_pointer_v<FirstArgumentPointer>); |
117 | static_assert(std::is_aggregate_v<FirstArgument>); |
118 | |
119 | static int expectedTypeForRequest(int rtype) |
120 | { |
121 | static_assert(RTM_NEWADDR == RTM_GETADDR - 2); |
122 | static_assert(RTM_NEWLINK == RTM_GETLINK - 2); |
123 | Q_ASSERT(rtype == RTM_GETADDR || rtype == RTM_GETLINK); |
124 | return rtype - 2; |
125 | } |
126 | |
127 | void operator()(int sock, nlmsghdr *hdr, char *buf, size_t bufsize, Lambda &&func) |
128 | { |
129 | // send the request |
130 | if (send(fd: sock, buf: hdr, n: hdr->nlmsg_len, flags: 0) != ssize_t(hdr->nlmsg_len)) |
131 | return; |
132 | |
133 | // receive and parse the request |
134 | int expectedType = expectedTypeForRequest(rtype: hdr->nlmsg_type); |
135 | const bool isDump = hdr->nlmsg_flags & NLM_F_DUMP; |
136 | forever { |
137 | qsizetype len = recv(fd: sock, buf: buf, n: bufsize, flags: 0); |
138 | hdr = reinterpret_cast<struct nlmsghdr *>(buf); |
139 | if (!NLMSG_OK(hdr, quint32(len))) |
140 | return; |
141 | |
142 | auto arg = static_cast<FirstArgument *>(NLMSG_DATA(hdr)); |
143 | size_t payloadLen = NLMSG_PAYLOAD(hdr, 0); |
144 | |
145 | // is this a multipart message? |
146 | Q_ASSERT(isDump == !!(hdr->nlmsg_flags & NLM_F_MULTI)); |
147 | if (!isDump) { |
148 | // no, single message |
149 | if (hdr->nlmsg_type == expectedType && payloadLen >= sizeof(FirstArgument)) |
150 | return void(func(arg, payloadLen)); |
151 | } else { |
152 | // multipart, parse until done |
153 | do { |
154 | if (hdr->nlmsg_type == NLMSG_DONE) |
155 | return; |
156 | if (hdr->nlmsg_type != expectedType || payloadLen < sizeof(FirstArgument)) |
157 | break; |
158 | func(arg, payloadLen); |
159 | |
160 | // NLMSG_NEXT also updates the len variable |
161 | hdr = NLMSG_NEXT(hdr, len); |
162 | arg = static_cast<FirstArgument *>(NLMSG_DATA(hdr)); |
163 | payloadLen = NLMSG_PAYLOAD(hdr, 0); |
164 | } while (NLMSG_OK(hdr, quint32(len))); |
165 | |
166 | if (len == 0) |
167 | continue; // get new datagram |
168 | } |
169 | |
170 | #ifndef QT_NO_DEBUG |
171 | if (NLMSG_OK(hdr, quint32(len))) |
172 | qWarning(msg: "QNetworkInterface/AF_NETLINK: received unknown packet type (%d) or too short (%u)" , |
173 | hdr->nlmsg_type, hdr->nlmsg_len); |
174 | else |
175 | qWarning(msg: "QNetworkInterface/AF_NETLINK: received invalid packet with size %d" , int(len)); |
176 | #endif |
177 | return; |
178 | } |
179 | } |
180 | }; |
181 | |
182 | template <typename Lambda> |
183 | void processNetlinkRequest(int sock, struct nlmsghdr *hdr, char *buf, size_t bufsize, Lambda &&l) |
184 | { |
185 | ProcessNetlinkRequest<Lambda>()(sock, hdr, buf, bufsize, std::forward<Lambda>(l)); |
186 | } |
187 | } |
188 | |
189 | uint QNetworkInterfaceManager::interfaceIndexFromName(const QString &name) |
190 | { |
191 | uint index = 0; |
192 | if (name.size() >= IFNAMSIZ) |
193 | return index; |
194 | |
195 | int socket = qt_safe_socket(AF_INET, SOCK_DGRAM, protocol: 0); |
196 | if (socket >= 0) { |
197 | struct ifreq req; |
198 | req.ifr_ifindex = 0; |
199 | strcpy(dest: req.ifr_name, src: name.toLatin1().constData()); |
200 | |
201 | if (qt_safe_ioctl(sockfd: socket, SIOCGIFINDEX, arg: &req) >= 0) |
202 | index = req.ifr_ifindex; |
203 | qt_safe_close(fd: socket); |
204 | } |
205 | return index; |
206 | } |
207 | |
208 | QString QNetworkInterfaceManager::interfaceNameFromIndex(uint index) |
209 | { |
210 | int socket = qt_safe_socket(AF_INET, SOCK_DGRAM, protocol: 0); |
211 | if (socket >= 0) { |
212 | struct ifreq req; |
213 | req.ifr_ifindex = index; |
214 | |
215 | if (qt_safe_ioctl(sockfd: socket, SIOCGIFNAME, arg: &req) >= 0) { |
216 | qt_safe_close(fd: socket); |
217 | return QString::fromLatin1(ba: req.ifr_name); |
218 | } |
219 | qt_safe_close(fd: socket); |
220 | } |
221 | return QString(); |
222 | } |
223 | |
224 | static QList<QNetworkInterfacePrivate *> getInterfaces(int sock, char *buf) |
225 | { |
226 | QList<QNetworkInterfacePrivate *> result; |
227 | struct ifreq req; |
228 | |
229 | // request all links |
230 | struct { |
231 | struct nlmsghdr req; |
232 | struct ifinfomsg ifi; |
233 | } ifi_req; |
234 | memset(s: &ifi_req, c: 0, n: sizeof(ifi_req)); |
235 | |
236 | ifi_req.req.nlmsg_len = sizeof(ifi_req); |
237 | ifi_req.req.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; |
238 | ifi_req.req.nlmsg_type = RTM_GETLINK; |
239 | |
240 | // parse the interfaces |
241 | processNetlinkRequest(sock, hdr: &ifi_req.req, buf, bufsize: BufferSize, l: [&](ifinfomsg *ifi, size_t len) { |
242 | auto iface = new QNetworkInterfacePrivate; |
243 | iface->index = ifi->ifi_index; |
244 | iface->flags = convertFlags(rawFlags: ifi->ifi_flags); |
245 | |
246 | // read attributes |
247 | auto rta = reinterpret_cast<struct rtattr *>(ifi + 1); |
248 | len -= sizeof(*ifi); |
249 | for ( ; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { |
250 | int payloadLen = RTA_PAYLOAD(rta); |
251 | auto payloadPtr = reinterpret_cast<char *>(RTA_DATA(rta)); |
252 | |
253 | switch (rta->rta_type) { |
254 | case IFLA_ADDRESS: // link-level address |
255 | iface->hardwareAddress = |
256 | iface->makeHwAddress(len: payloadLen, data: reinterpret_cast<uchar *>(payloadPtr)); |
257 | break; |
258 | |
259 | case IFLA_IFNAME: // interface name |
260 | Q_ASSERT(payloadLen <= int(sizeof(req.ifr_name))); |
261 | memcpy(dest: req.ifr_name, src: payloadPtr, n: payloadLen); // including terminating NUL |
262 | iface->name = QString::fromLatin1(str: payloadPtr, size: payloadLen - 1); |
263 | break; |
264 | |
265 | case IFLA_MTU: |
266 | Q_ASSERT(payloadLen == sizeof(int)); |
267 | iface->mtu = *reinterpret_cast<int *>(payloadPtr); |
268 | break; |
269 | |
270 | case IFLA_OPERSTATE: // operational state |
271 | if (*payloadPtr != IF_OPER_UNKNOWN) { |
272 | // override the flag |
273 | iface->flags &= ~QNetworkInterface::IsRunning; |
274 | if (*payloadPtr == IF_OPER_UP) |
275 | iface->flags |= QNetworkInterface::IsRunning; |
276 | } |
277 | break; |
278 | } |
279 | } |
280 | |
281 | if (Q_UNLIKELY(iface->name.isEmpty())) { |
282 | qWarning(msg: "QNetworkInterface: found interface %d with no name" , iface->index); |
283 | delete iface; |
284 | } else { |
285 | iface->type = probeIfType(socket: sock, req: &req, arptype: ifi->ifi_type); |
286 | result.append(t: iface); |
287 | } |
288 | }); |
289 | return result; |
290 | } |
291 | |
292 | static void getAddresses(int sock, char *buf, QList<QNetworkInterfacePrivate *> &result) |
293 | { |
294 | // request all addresses |
295 | struct { |
296 | struct nlmsghdr req; |
297 | struct ifaddrmsg ifa; |
298 | } ifa_req; |
299 | memset(s: &ifa_req, c: 0, n: sizeof(ifa_req)); |
300 | |
301 | ifa_req.req.nlmsg_len = sizeof(ifa_req); |
302 | ifa_req.req.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; |
303 | ifa_req.req.nlmsg_type = RTM_GETADDR; |
304 | ifa_req.req.nlmsg_seq = 1; |
305 | |
306 | // parse the addresses |
307 | processNetlinkRequest(sock, hdr: &ifa_req.req, buf, bufsize: BufferSize, l: [&](ifaddrmsg *ifa, size_t len) { |
308 | if (Q_UNLIKELY(ifa->ifa_family != AF_INET && ifa->ifa_family != AF_INET6)) { |
309 | // unknown address types |
310 | return; |
311 | } |
312 | |
313 | // find the interface this is relevant to |
314 | QNetworkInterfacePrivate *iface = nullptr; |
315 | for (auto candidate : std::as_const(t&: result)) { |
316 | if (candidate->index != int(ifa->ifa_index)) |
317 | continue; |
318 | iface = candidate; |
319 | break; |
320 | } |
321 | |
322 | if (Q_UNLIKELY(!iface)) { |
323 | qWarning(msg: "QNetworkInterface/AF_NETLINK: found unknown interface with index %d" , ifa->ifa_index); |
324 | return; |
325 | } |
326 | |
327 | QNetworkAddressEntry entry; |
328 | quint32 flags = ifa->ifa_flags; // may be overwritten by IFA_FLAGS |
329 | |
330 | auto makeAddress = [=](uchar *ptr, int len) { |
331 | QHostAddress addr; |
332 | if (ifa->ifa_family == AF_INET) { |
333 | Q_ASSERT(len == 4); |
334 | addr.setAddress(qFromBigEndian<quint32>(src: ptr)); |
335 | } else { |
336 | Q_ASSERT(len == 16); |
337 | addr.setAddress(ptr); |
338 | |
339 | // do we need a scope ID? |
340 | if (addr.isLinkLocal()) |
341 | addr.setScopeId(iface->name); |
342 | } |
343 | return addr; |
344 | }; |
345 | |
346 | // read attributes |
347 | auto rta = reinterpret_cast<struct rtattr *>(ifa + 1); |
348 | len -= sizeof(*ifa); |
349 | for ( ; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { |
350 | int payloadLen = RTA_PAYLOAD(rta); |
351 | auto payloadPtr = reinterpret_cast<uchar *>(RTA_DATA(rta)); |
352 | |
353 | switch (rta->rta_type) { |
354 | case IFA_ADDRESS: |
355 | // Local address (all interfaces except for point-to-point) |
356 | if (entry.ip().isNull()) |
357 | entry.setIp(makeAddress(payloadPtr, payloadLen)); |
358 | break; |
359 | |
360 | case IFA_LOCAL: |
361 | // Override the local address (point-to-point interfaces) |
362 | entry.setIp(makeAddress(payloadPtr, payloadLen)); |
363 | break; |
364 | |
365 | case IFA_BROADCAST: |
366 | Q_ASSERT(ifa->ifa_family == AF_INET); |
367 | entry.setBroadcast(makeAddress(payloadPtr, payloadLen)); |
368 | break; |
369 | |
370 | case IFA_CACHEINFO: |
371 | if (size_t(payloadLen) >= sizeof(ifa_cacheinfo)) { |
372 | auto cacheinfo = reinterpret_cast<ifa_cacheinfo *>(payloadPtr); |
373 | auto toDeadline = [](quint32 lifetime) -> QDeadlineTimer { |
374 | if (lifetime == quint32(-1)) |
375 | return QDeadlineTimer::Forever; |
376 | return QDeadlineTimer(lifetime * 1000); |
377 | }; |
378 | entry.setAddressLifetime(preferred: toDeadline(cacheinfo->ifa_prefered), validity: toDeadline(cacheinfo->ifa_valid)); |
379 | } |
380 | break; |
381 | |
382 | case IFA_FLAGS: |
383 | Q_ASSERT(payloadLen == 4); |
384 | flags = qFromUnaligned<quint32>(src: payloadPtr); |
385 | break; |
386 | } |
387 | } |
388 | |
389 | if (ifa->ifa_family == AF_INET6 && (ifa->ifa_flags & IFA_F_DADFAILED)) |
390 | return; |
391 | |
392 | // now handle flags |
393 | QNetworkInterfacePrivate::calculateDnsEligibility(entry: &entry, |
394 | isTemporary: flags & IFA_F_TEMPORARY, |
395 | isDeprecated: flags & IFA_F_DEPRECATED); |
396 | |
397 | |
398 | if (!entry.ip().isNull()) { |
399 | entry.setPrefixLength(ifa->ifa_prefixlen); |
400 | iface->addressEntries.append(t: entry); |
401 | } |
402 | }); |
403 | } |
404 | |
405 | QList<QNetworkInterfacePrivate *> QNetworkInterfaceManager::scan() |
406 | { |
407 | // open netlink socket |
408 | QList<QNetworkInterfacePrivate *> result; |
409 | NetlinkSocket sock(BufferSize); |
410 | if (Q_UNLIKELY(sock == -1)) |
411 | return result; |
412 | |
413 | QByteArray buffer(BufferSize, Qt::Uninitialized); |
414 | char *buf = buffer.data(); |
415 | |
416 | result = getInterfaces(sock, buf); |
417 | getAddresses(sock, buf, result); |
418 | |
419 | return result; |
420 | } |
421 | |
422 | QT_END_NAMESPACE |
423 | |
424 | #endif // QT_NO_NETWORKINTERFACE |
425 | |