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,5,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
63bool PortChecker::checkPort(const QString &host, int port, std::chrono::milliseconds timeLimit)
64{
65 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("host %1 port %2 timeLimit %3")
66 .arg(host).arg(port).arg(timeLimit.count()));
67 m_cancelCheck = false;
68// Windows does not need the scope on the ip address so we can skip
69// some processing
70#ifndef Q_OS_WINDOWS
71 QHostAddress addr;
72 bool isIPAddress = addr.setAddress(host);
73 if (isIPAddress
74 && addr.protocol() == QAbstractSocket::IPv6Protocol
75 && addr.isInSubnet(QHostAddress::parseSubnet("fe80::/10")))
76 {
77 QString dest {host};
78 return resolveLinkLocal(dest, port, timeLimit);
79 }
80#endif
81 QTcpSocket socket;
82 socket.connectToHost(host, port);
84 QAbstractSocket::SocketState state = QAbstractSocket::UnconnectedState;
85 while (state != QAbstractSocket::ConnectedState
86 && (timer.elapsed() < timeLimit)
88 )
89 {
90 static constexpr std::chrono::milliseconds k_poll_interval {1ms};
91 QCoreApplication::processEvents(QEventLoop::AllEvents, k_poll_interval.count());
92 std::this_thread::sleep_for(1ns); // force thread to yield
93 state = socket.state();
94 }
95 state = socket.state();
96 LOG(VB_GENERAL, LOG_DEBUG, LOC +
97 QString("host %1 port %2 socket state %3, attempt time: %4")
98 .arg(host, QString::number(port), QString::number(state),
99 QString::number(timer.elapsed().count())
100 )
101 );
102 return (state == QAbstractSocket::ConnectedState);
103}
104
140bool PortChecker::resolveLinkLocal(QString &host, int port, std::chrono::milliseconds timeLimit)
141{
142 // Windows does not need the scope on the ip address so we can skip
143 // some processing
144#ifdef Q_OS_WINDOWS
145 return false;
146#else
147 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("host %1 port %2 timeLimit %3")
148 .arg(host).arg(port).arg(timeLimit.count()));
149 m_cancelCheck = false;
150 QHostAddress addr;
151 bool isIPAddress = addr.setAddress(host);
152 if (isIPAddress
153 && addr.protocol() == QAbstractSocket::IPv6Protocol
154 && addr.isInSubnet(QHostAddress::parseSubnet("fe80::/10")))
155 {
156 // If we already know the scope, set it here and return
158 {
159 host = addr.toString();
160 return true;
161 }
162 }
163 else
164 {
165 return false;
166 }
167 QList<QNetworkInterface> cards = QNetworkInterface::allInterfaces();
168 auto iCard = cards.cbegin();
170 QAbstractSocket::SocketState state = QAbstractSocket::UnconnectedState;
171 int iCardsEnd = 0;
172 while (state != QAbstractSocket::ConnectedState
173 && (timer.elapsed() < timeLimit)
174 && !m_cancelCheck
175 )
176 {
177 // Determine the IPv6 scope to check
178 addr.setScopeId(QString());
179 while (addr.scopeId().isEmpty())
180 {
181 // search for the next available IPV6 interface.
182 if (iCard != cards.cend())
183 {
184 unsigned int flags = iCard->flags();
185 if (!(flags & QNetworkInterface::IsLoopBack)
186 && (flags & QNetworkInterface::IsRunning))
187 {
188 // check that IPv6 is enabled on that interface
189 QList<QNetworkAddressEntry> addresses = iCard->addressEntries();
190 for (const auto& ae : std::as_const(addresses))
191 {
192 if (ae.ip().protocol() == QAbstractSocket::IPv6Protocol)
193 {
194 addr.setScopeId(iCard->name());
195 break;
196 }
197 }
198 }
199 iCard++;
200 }
201 else
202 {
203 iCardsEnd++;
204 if (iCardsEnd >= 2)
205 {
206 LOG(VB_GENERAL, LOG_ERR, LOC +
207 QString("There is no IPV6 compatible interface for %1").arg(host)
208 );
209 return false;
210 }
211 // Get a new list in case a new interface
212 // has been added.
213 cards = QNetworkInterface::allInterfaces();
214 iCard = cards.cbegin();
215 }
216 }
217 LOG(VB_GENERAL, LOG_DEBUG, QString("Checking host %1 port %2")
218 .arg(addr.toString(), QString::number(port))
219 );
220 QTcpSocket socket;
221 socket.connectToHost(addr.toString(), port);
222 std::chrono::milliseconds attempt_time_limit
223 {std::min(3s + timer.elapsed(), timeLimit)};
224 while (state != QAbstractSocket::ConnectedState
225 && !m_cancelCheck
226 && timer.elapsed() < attempt_time_limit
227 )
228 {
229 static constexpr std::chrono::milliseconds k_poll_interval {1ms};
230 QCoreApplication::processEvents(QEventLoop::AllEvents, k_poll_interval.count());
231 std::this_thread::sleep_for(1ns); // force thread to yield
232 state = socket.state();
233 }
234 state = socket.state();
235 LOG(VB_GENERAL, LOG_DEBUG, LOC +
236 QString("host %1 port %2 socket state %3, attempt time: %4")
237 .arg(host, QString::number(port), QString::number(state),
238 QString::number(timer.elapsed().count())
239 )
240 );
241 }
242 if (state == QAbstractSocket::ConnectedState && !addr.scopeId().isEmpty())
243 {
245 host = addr.toString();
246 }
247 return (state == QAbstractSocket::ConnectedState);
248#endif
249}
250
259{
260 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Aborting port check"));
261 m_cancelCheck = true;
262}
263
264
265/* 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
bool m_cancelCheck
Definition: portchecker.h:60
bool resolveLinkLocal(QString &host, int port, std::chrono::milliseconds timeLimit=30s)
Convenience method to resolve link-local address.
void cancelPortCheck(void)
Cancel the checkPort operation currently in progress.
bool checkPort(const QString &host, int port, std::chrono::milliseconds timeLimit=30s)
Check if a port is open.
Definition: portchecker.cpp:63
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