MythTV master
portchecker.cpp
Go to the documentation of this file.
1
2// Copyright (c) 2017 MythTV Developers <mythtv-dev@mythtv.org>
3//
4// This is part of MythTV (https://www.mythtv.org)
5//
6// This program is free software; you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation; either version 2 of the License, or
9// (at your option) any later version.
10//
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License
17// along with this program; if not, write to the Free Software
18// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19//
20// You should have received a copy of the GNU General Public License
21// along with this program. If not, see <http://www.gnu.org/licenses/>.
22//
24
25#include <QtGlobal>
26#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
27#include <QtSystemDetection>
28#endif
29#include <QCoreApplication>
30#include <QHostAddress>
31#include <QTcpSocket>
32#include <QEventLoop>
33#include <QNetworkInterface>
34#include <QNetworkAddressEntry>
35
36#include <thread>
37
38#include "mythcorecontext.h"
39#include "mythlogging.h"
40#include "mythtimer.h"
41#include "portchecker.h"
42
43#define LOC QString("PortChecker::%1(): ").arg(__func__)
44
78bool PortChecker::checkPort(QString &host, int port, std::chrono::milliseconds timeLimit, bool linkLocalOnly)
79{
80 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("host %1 port %2 timeLimit %3 linkLocalOnly %4")
81 .arg(host).arg(port).arg(timeLimit.count()).arg(linkLocalOnly));
82 m_cancelCheck = false;
83 QHostAddress addr;
84 bool isIPAddress = addr.setAddress(host);
85 bool islinkLocal = false;
86// Windows does not need the scope on the ip address so we can skip
87// some processing
88#ifndef Q_OS_WINDOWS
89 if (isIPAddress
90 && addr.protocol() == QAbstractSocket::IPv6Protocol
91 && addr.isInSubnet(QHostAddress::parseSubnet("fe80::/10")))
92 islinkLocal = true;
93#endif
94 if (linkLocalOnly)
95 {
96 if (islinkLocal)
97 {
98 // If we already know the scope, set it here and return
100 {
101 host = addr.toString();
102 return true;
103 }
104 }
105 else
106 {
107 return false;
108 }
109 }
110 QList<QNetworkInterface> cards = QNetworkInterface::allInterfaces();
111#ifndef Q_OS_WINDOWS
112 QListIterator<QNetworkInterface> iCard = cards;
113#endif
115 QTcpSocket socket(this);
116 QAbstractSocket::SocketState state = QAbstractSocket::UnconnectedState;
117 int retryCount = 0;
118 QString scope;
119 bool testedAll = false;
120 while (state != QAbstractSocket::ConnectedState
121 && (timer.elapsed() < timeLimit))
122 {
123 if (state == QAbstractSocket::UnconnectedState)
124 {
125// Windows does not need the scope on the ip address so we can skip
126// some processing
127#ifndef Q_OS_WINDOWS
128 int iCardsEnd = 0;
129 if (islinkLocal && !gCoreContext->GetScopeForAddress(addr))
130 {
131 addr.setScopeId(QString());
132 while (addr.scopeId().isEmpty() && iCardsEnd<2)
133 {
134 // search for the next available IPV6 interface.
135 if (iCard.hasNext())
136 {
137 QNetworkInterface card = iCard.next();
138 LOG(VB_GENERAL, LOG_DEBUG, QString("Trying interface %1").arg(card.name()));
139 unsigned int flags = card.flags();
140 if ((flags & QNetworkInterface::IsLoopBack)
141 || !(flags & QNetworkInterface::IsRunning))
142 continue;
143 // check that IPv6 is enabled on that interface
144 QList<QNetworkAddressEntry> addresses = card.addressEntries();
145 bool foundv6 = false;
146 for (const auto& ae : std::as_const(addresses))
147 {
148 if (ae.ip().protocol() == QAbstractSocket::IPv6Protocol)
149 {
150 foundv6 = true;
151 break;
152 }
153 }
154 if (foundv6)
155 {
156 scope = card.name();
157 addr.setScopeId(scope);
158 break;
159 }
160 }
161 else
162 {
163 // Get a new list in case a new interface
164 // has been added.
165 cards = QNetworkInterface::allInterfaces();
166 iCard = cards;
167 iCard.toFront();
168 testedAll=true;
169 iCardsEnd++;
170 }
171 }
172 }
173 if (iCardsEnd > 1)
174 {
175 LOG(VB_GENERAL, LOG_ERR, LOC + QString("There is no IPV6 compatible interface for %1")
176 .arg(host));
177 break;
178 }
179#endif
180 QString dest;
181 if (isIPAddress)
182 dest=addr.toString();
183 else
184 dest=host;
185 socket.connectToHost(dest, port);
186 retryCount=0;
187 }
188 else
189 {
190 retryCount++;
191 }
192 // This retry count of 6 means 3 seconds of waiting for
193 // connection before aborting and starting a new connection attempt.
194 if (retryCount > 6)
195 socket.abort();
197 // Check if user got impatient and canceled
198 if (m_cancelCheck)
199 break;
200 std::this_thread::sleep_for(500ms);
201 state = socket.state();
202 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("socket state %1")
203 .arg(state));
204 if (linkLocalOnly
205 && state == QAbstractSocket::UnconnectedState
206 && testedAll)
207 break;
208 }
209 if (state == QAbstractSocket::ConnectedState
210 && islinkLocal && !scope.isEmpty())
211 {
213 host = addr.toString();
214 }
215 socket.abort();
217 return (state == QAbstractSocket::ConnectedState);
218}
219
234// static method
235bool PortChecker::resolveLinkLocal(QString &host, int port, std::chrono::milliseconds timeLimit)
236{
237 PortChecker checker;
238 return checker.checkPort(host,port,timeLimit,true);
239}
240
242{
243 qApp->processEvents(QEventLoop::AllEvents, 250);
244 qApp->processEvents(QEventLoop::AllEvents, 250);
245}
246
255{
256 m_cancelCheck = true;
257}
258
259
260/* vim: set expandtab tabstop=4 shiftwidth=4: */
void SetScopeForAddress(const QHostAddress &addr)
Record the scope Id of the given IP address.
bool GetScopeForAddress(QHostAddress &addr) const
Return the cached scope Id for the given address.
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:14
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:91
@ kStartRunning
Definition: mythtimer.h:17
Small class to handle TCP port checking and finding link-local context.
Definition: portchecker.h:45
bool m_cancelCheck
Definition: portchecker.h:61
static bool resolveLinkLocal(QString &host, int port, std::chrono::milliseconds timeLimit=30s)
Convenience method to resolve link-local address.
static void processEvents(void)
bool checkPort(QString &host, int port, std::chrono::milliseconds timeLimit=30s, bool linkLocalOnly=false)
Check if a port is open and sort out the link-local scope.
Definition: portchecker.cpp:78
void cancelPortCheck(void)
Cancel the checkPort operation currently in progress.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
#define LOC
Definition: portchecker.cpp:43