1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Copyright (C) 2016 Intel Corporation. |
5 | ** Contact: https://www.qt.io/licensing/ |
6 | ** |
7 | ** This file is part of the test suite of the Qt Toolkit. |
8 | ** |
9 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
10 | ** Commercial License Usage |
11 | ** Licensees holding valid commercial Qt licenses may use this file in |
12 | ** accordance with the commercial license agreement provided with the |
13 | ** Software or, alternatively, in accordance with the terms contained in |
14 | ** a written agreement between you and The Qt Company. For licensing terms |
15 | ** and conditions see https://www.qt.io/terms-conditions. For further |
16 | ** information use the contact form at https://www.qt.io/contact-us. |
17 | ** |
18 | ** GNU General Public License Usage |
19 | ** Alternatively, this file may be used under the terms of the GNU |
20 | ** General Public License version 3 as published by the Free Software |
21 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
22 | ** included in the packaging of this file. Please review the following |
23 | ** information to ensure the GNU General Public License requirements will |
24 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
25 | ** |
26 | ** $QT_END_LICENSE$ |
27 | ** |
28 | ****************************************************************************/ |
29 | |
30 | |
31 | #include <QtTest/QtTest> |
32 | |
33 | #include <qcoreapplication.h> |
34 | #include <qnetworkinterface.h> |
35 | #include <qudpsocket.h> |
36 | #ifndef QT_NO_BEARERMANAGEMENT |
37 | #include <QNetworkConfigurationManager> |
38 | #include <QNetworkSession> |
39 | #endif |
40 | #include "../../../network-settings.h" |
41 | #include "emulationdetector.h" |
42 | |
43 | Q_DECLARE_METATYPE(QHostAddress) |
44 | |
45 | class tst_QNetworkInterface : public QObject |
46 | { |
47 | Q_OBJECT |
48 | |
49 | public: |
50 | tst_QNetworkInterface(); |
51 | virtual ~tst_QNetworkInterface(); |
52 | |
53 | bool isIPv6Working(); |
54 | |
55 | private slots: |
56 | void initTestCase(); |
57 | void cleanupTestCase(); |
58 | void dump(); |
59 | void consistencyCheck(); |
60 | void loopbackIPv4(); |
61 | void loopbackIPv6(); |
62 | void localAddress_data(); |
63 | void localAddress(); |
64 | void interfaceFromXXX_data(); |
65 | void interfaceFromXXX(); |
66 | void copyInvalidInterface(); |
67 | |
68 | private: |
69 | #ifndef QT_NO_BEARERMANAGEMENT |
70 | QNetworkConfigurationManager *netConfMan; |
71 | QNetworkConfiguration networkConfiguration; |
72 | QScopedPointer<QNetworkSession> networkSession; |
73 | #endif |
74 | }; |
75 | |
76 | tst_QNetworkInterface::tst_QNetworkInterface() |
77 | { |
78 | } |
79 | |
80 | tst_QNetworkInterface::~tst_QNetworkInterface() |
81 | { |
82 | } |
83 | |
84 | bool tst_QNetworkInterface::isIPv6Working() |
85 | { |
86 | QUdpSocket socket; |
87 | socket.connectToHost(address: QHostAddress::LocalHostIPv6, port: 1234); |
88 | return socket.state() == QAbstractSocket::ConnectedState || socket.waitForConnected(msecs: 100); |
89 | } |
90 | |
91 | void tst_QNetworkInterface::initTestCase() |
92 | { |
93 | if (!QtNetworkSettings::verifyTestNetworkSettings()) |
94 | QSKIP("No network test server available" ); |
95 | #ifndef QT_NO_BEARERMANAGEMENT |
96 | netConfMan = new QNetworkConfigurationManager(this); |
97 | if (netConfMan->capabilities() |
98 | & QNetworkConfigurationManager::NetworkSessionRequired) { |
99 | networkConfiguration = netConfMan->defaultConfiguration(); |
100 | networkSession.reset(other: new QNetworkSession(networkConfiguration)); |
101 | if (!networkSession->isOpen()) { |
102 | networkSession->open(); |
103 | QVERIFY(networkSession->waitForOpened(30000)); |
104 | } |
105 | } |
106 | #endif |
107 | } |
108 | |
109 | void tst_QNetworkInterface::cleanupTestCase() |
110 | { |
111 | #ifndef QT_NO_BEARERMANAGEMENT |
112 | if (networkSession && networkSession->isOpen()) { |
113 | networkSession->close(); |
114 | } |
115 | #endif |
116 | } |
117 | |
118 | void tst_QNetworkInterface::dump() |
119 | { |
120 | // This is for manual testing: |
121 | QList<QNetworkInterface> allInterfaces = QNetworkInterface::allInterfaces(); |
122 | foreach (const QNetworkInterface &i, allInterfaces) { |
123 | QString flags; |
124 | if (i.flags() & QNetworkInterface::IsUp) flags += "Up," ; |
125 | if (i.flags() & QNetworkInterface::IsRunning) flags += "Running," ; |
126 | if (i.flags() & QNetworkInterface::CanBroadcast) flags += "Broadcast," ; |
127 | if (i.flags() & QNetworkInterface::IsLoopBack) flags += "Loopback," ; |
128 | if (i.flags() & QNetworkInterface::IsPointToPoint) flags += "PointToPoint," ; |
129 | if (i.flags() & QNetworkInterface::CanMulticast) flags += "Multicast," ; |
130 | flags.chop(n: 1); // drop last comma |
131 | |
132 | QString friendlyName = i.humanReadableName(); |
133 | if (friendlyName != i.name()) { |
134 | friendlyName.prepend(c: '('); |
135 | friendlyName.append(c: ')'); |
136 | } else { |
137 | friendlyName.clear(); |
138 | } |
139 | qDebug() << "Interface: " << i.name() << qPrintable(friendlyName); |
140 | QVERIFY(i.isValid()); |
141 | |
142 | qDebug() << " index: " << i.index(); |
143 | qDebug() << " flags: " << qPrintable(flags); |
144 | qDebug() << " type: " << i.type(); |
145 | qDebug() << " hw address:" << qPrintable(i.hardwareAddress()); |
146 | qDebug() << " MTU: " << i.maximumTransmissionUnit(); |
147 | |
148 | int count = 0; |
149 | foreach (const QNetworkAddressEntry &e, i.addressEntries()) { |
150 | QDebug s = qDebug(); |
151 | s.nospace() << " address " |
152 | << qSetFieldWidth(width: 2) << count++ << qSetFieldWidth(width: 0); |
153 | s.nospace() << ": " << qPrintable(e.ip().toString()); |
154 | if (!e.netmask().isNull()) |
155 | s.nospace() << '/' << e.prefixLength() |
156 | << " (" << qPrintable(e.netmask().toString()) << ')'; |
157 | if (!e.broadcast().isNull()) |
158 | s.nospace() << " broadcast " << qPrintable(e.broadcast().toString()); |
159 | if (e.dnsEligibility() == QNetworkAddressEntry::DnsEligible) |
160 | s.nospace() << " dns-eligible" ; |
161 | else if (e.dnsEligibility() == QNetworkAddressEntry::DnsIneligible) |
162 | s.nospace() << " dns-ineligible" ; |
163 | if (e.isLifetimeKnown()) { |
164 | #define printable(l) qPrintable(l.isForever() ? "forever" : QString::fromLatin1("%1ms").arg(l.remainingTime())) |
165 | s.nospace() << " preferred:" << printable(e.preferredLifetime()) |
166 | << " valid:" << printable(e.validityLifetime()); |
167 | #undef printable |
168 | } |
169 | } |
170 | } |
171 | } |
172 | |
173 | void tst_QNetworkInterface::consistencyCheck() |
174 | { |
175 | QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces(); |
176 | QSet<QString> interfaceNames; |
177 | QVector<int> interfaceIndexes; |
178 | |
179 | foreach (const QNetworkInterface &iface, ifaces) { |
180 | QVERIFY(iface.isValid()); |
181 | QVERIFY2(!interfaceNames.contains(iface.name()), |
182 | "duplicate name = " + iface.name().toLocal8Bit()); |
183 | interfaceNames << iface.name(); |
184 | |
185 | QVERIFY2(!interfaceIndexes.contains(iface.index()), |
186 | "duplicate index = " + QByteArray::number(iface.index())); |
187 | if (iface.index()) |
188 | interfaceIndexes << iface.index(); |
189 | |
190 | QVERIFY(iface.maximumTransmissionUnit() >= 0); |
191 | |
192 | const QList<QNetworkAddressEntry> addresses = iface.addressEntries(); |
193 | for (auto entry : addresses) { |
194 | QVERIFY(entry.ip().protocol() != QAbstractSocket::UnknownNetworkLayerProtocol); |
195 | if (!entry.preferredLifetime().isForever() || !entry.validityLifetime().isForever()) |
196 | QVERIFY(entry.isLifetimeKnown()); |
197 | if (!entry.validityLifetime().isForever()) |
198 | QVERIFY(entry.isTemporary()); |
199 | } |
200 | } |
201 | } |
202 | |
203 | void tst_QNetworkInterface::loopbackIPv4() |
204 | { |
205 | QList<QHostAddress> all = QNetworkInterface::allAddresses(); |
206 | QVERIFY(all.contains(QHostAddress(QHostAddress::LocalHost))); |
207 | } |
208 | |
209 | void tst_QNetworkInterface::loopbackIPv6() |
210 | { |
211 | if (!isIPv6Working()) |
212 | QSKIP("IPv6 not active on this machine" ); |
213 | QList<QHostAddress> all = QNetworkInterface::allAddresses(); |
214 | QVERIFY(all.contains(QHostAddress(QHostAddress::LocalHostIPv6))); |
215 | } |
216 | void tst_QNetworkInterface::localAddress_data() |
217 | { |
218 | bool ipv6 = isIPv6Working(); |
219 | QTest::addColumn<QHostAddress>(name: "target" ); |
220 | |
221 | QTest::newRow(dataTag: "localhost-ipv4" ) << QHostAddress(QHostAddress::LocalHost); |
222 | if (ipv6) |
223 | QTest::newRow(dataTag: "localhost-ipv6" ) << QHostAddress(QHostAddress::LocalHostIPv6); |
224 | |
225 | QTest::newRow(dataTag: "test-server" ) << QtNetworkSettings::serverIP(); |
226 | |
227 | QSet<QHostAddress> added; |
228 | const QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces(); |
229 | for (const QNetworkInterface &iface : ifaces) { |
230 | if ((iface.flags() & QNetworkInterface::IsUp) == 0) |
231 | continue; |
232 | const QList<QNetworkAddressEntry> addrs = iface.addressEntries(); |
233 | for (const QNetworkAddressEntry &entry : addrs) { |
234 | QHostAddress addr = entry.ip(); |
235 | if (addr.isLoopback()) |
236 | continue; // added above |
237 | |
238 | if (addr.protocol() == QAbstractSocket::IPv4Protocol) { |
239 | // add an IPv4 address with bits in the host portion of the address flipped |
240 | quint32 ip4 = entry.ip().toIPv4Address(); |
241 | addr.setAddress(ip4 ^ ~entry.netmask().toIPv4Address()); |
242 | } else if (!ipv6 || entry.prefixLength() != 64) { |
243 | continue; |
244 | } else { |
245 | // add a random node in this IPv6 network |
246 | quint64 randomid = qFromBigEndian(Q_UINT64_C(0x8f41f072e5733caa)); |
247 | QIPv6Address ip6 = addr.toIPv6Address(); |
248 | memcpy(dest: &ip6[8], src: &randomid, n: sizeof(randomid)); |
249 | addr.setAddress(ip6); |
250 | } |
251 | |
252 | if (added.contains(value: addr)) |
253 | continue; |
254 | added.insert(value: addr); |
255 | |
256 | QTest::addRow(format: "%s-%s" , qPrintable(iface.name()), qPrintable(addr.toString())) << addr; |
257 | } |
258 | } |
259 | } |
260 | |
261 | void tst_QNetworkInterface::localAddress() |
262 | { |
263 | QFETCH(QHostAddress, target); |
264 | QUdpSocket socket; |
265 | socket.connectToHost(address: target, port: 80); |
266 | QVERIFY(socket.waitForConnected(5000)); |
267 | |
268 | QHostAddress local = socket.localAddress(); |
269 | |
270 | // find the interface that contains the address QUdpSocket reported |
271 | QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces(); |
272 | const QNetworkInterface *outgoingIface = nullptr; |
273 | for (const QNetworkInterface &iface : ifaces) { |
274 | QList<QNetworkAddressEntry> addrs = iface.addressEntries(); |
275 | for (const QNetworkAddressEntry &entry : addrs) { |
276 | if (entry.ip() == local) { |
277 | outgoingIface = &iface; |
278 | break; |
279 | } |
280 | } |
281 | if (outgoingIface) |
282 | break; |
283 | } |
284 | QVERIFY(outgoingIface); |
285 | |
286 | // we get QVariant() if the QNativeSocketEngine doesn't know how to get the PMTU |
287 | int pmtu = socket.socketOption(option: QAbstractSocket::PathMtuSocketOption).toInt(); |
288 | qDebug() << "Connected to" << target.toString() << "via interface" << outgoingIface->name() |
289 | << "pmtu" << pmtu; |
290 | |
291 | // check that the Path MTU is less than or equal the interface's MTU |
292 | QVERIFY(pmtu <= outgoingIface->maximumTransmissionUnit()); |
293 | } |
294 | |
295 | void tst_QNetworkInterface::interfaceFromXXX_data() |
296 | { |
297 | QTest::addColumn<QNetworkInterface>(name: "iface" ); |
298 | |
299 | QList<QNetworkInterface> allInterfaces = QNetworkInterface::allInterfaces(); |
300 | if (allInterfaces.count() == 0) |
301 | QSKIP("No interfaces to test!" ); |
302 | foreach (QNetworkInterface iface, allInterfaces) |
303 | QTest::newRow(dataTag: iface.name().toLocal8Bit()) << iface; |
304 | } |
305 | |
306 | void tst_QNetworkInterface::interfaceFromXXX() |
307 | { |
308 | QFETCH(QNetworkInterface, iface); |
309 | |
310 | QVERIFY(QNetworkInterface::interfaceFromName(iface.name()).isValid()); |
311 | if (int idx = iface.index()) { |
312 | QVERIFY(QNetworkInterface::interfaceFromIndex(idx).isValid()); |
313 | if (EmulationDetector::isRunningArmOnX86()) |
314 | QEXPECT_FAIL("" , "SIOCGIFNAME fails on QEMU" , Continue); |
315 | QCOMPARE(QNetworkInterface::interfaceNameFromIndex(idx), iface.name()); |
316 | QCOMPARE(QNetworkInterface::interfaceIndexFromName(iface.name()), idx); |
317 | } |
318 | foreach (QNetworkAddressEntry entry, iface.addressEntries()) { |
319 | QVERIFY(!entry.ip().isNull()); |
320 | |
321 | if (!entry.netmask().isNull()) { |
322 | QCOMPARE(entry.netmask().protocol(), entry.ip().protocol()); |
323 | |
324 | // if the netmask is known, the broadcast is known |
325 | // but only for IPv4 (there is no such thing as broadcast in IPv6) |
326 | if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) { |
327 | QVERIFY(!entry.broadcast().isNull()); |
328 | } |
329 | } |
330 | |
331 | if (!entry.broadcast().isNull()) |
332 | QCOMPARE(entry.broadcast().protocol(), entry.ip().protocol()); |
333 | } |
334 | } |
335 | |
336 | void tst_QNetworkInterface::copyInvalidInterface() |
337 | { |
338 | // Force a copy of an interfaces that isn't likely to exist |
339 | QNetworkInterface i = QNetworkInterface::interfaceFromName(name: "plopp" ); |
340 | QVERIFY(!i.isValid()); |
341 | |
342 | QCOMPARE(i.index(), 0); |
343 | QVERIFY(i.name().isEmpty()); |
344 | QVERIFY(i.humanReadableName().isEmpty()); |
345 | QVERIFY(i.hardwareAddress().isEmpty()); |
346 | QCOMPARE(int(i.flags()), 0); |
347 | QVERIFY(i.addressEntries().isEmpty()); |
348 | } |
349 | |
350 | QTEST_MAIN(tst_QNetworkInterface) |
351 | #include "tst_qnetworkinterface.moc" |
352 | |