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