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 <QCoreApplication>
26#include <QHostAddress>
27#include <QTcpSocket>
28#include <QThread>
29#include <QEventLoop>
30#include <QNetworkInterface>
31#include <QNetworkAddressEntry>
32
33#include <thread>
34
35#include "mythcorecontext.h"
36#include "mythlogging.h"
37#include "mythtimer.h"
38#include "portchecker.h"
39
40#define LOC QString("PortChecker::%1(): ").arg(__func__)
41
75bool PortChecker::checkPort(QString &host, int port, std::chrono::milliseconds timeLimit, bool linkLocalOnly)
76{
77 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("host %1 port %2 timeLimit %3 linkLocalOnly %4")
78 .arg(host).arg(port).arg(timeLimit.count()).arg(linkLocalOnly));
79 m_cancelCheck = false;
80 QHostAddress addr;
81 bool isIPAddress = addr.setAddress(host);
82 bool islinkLocal = false;
83// Windows does not need the scope on the ip address so we can skip
84// some processing
85#ifndef _WIN32
86 if (isIPAddress
87 && addr.protocol() == QAbstractSocket::IPv6Protocol
88 && addr.isInSubnet(QHostAddress::parseSubnet("fe80::/10")))
89 islinkLocal = true;
90#endif
91 if (linkLocalOnly)
92 {
93 if (islinkLocal)
94 {
95 // If we already know the scope, set it here and return
97 {
98 host = addr.toString();
99 return true;
100 }
101 }
102 else
103 {
104 return false;
105 }
106 }
107 QList<QNetworkInterface> cards = QNetworkInterface::allInterfaces();
108#ifndef _WIN32
109 QListIterator<QNetworkInterface> iCard = cards;
110#endif
112 QTcpSocket socket(this);
113 QAbstractSocket::SocketState state = QAbstractSocket::UnconnectedState;
114 QString scope;
115 bool testedAll = false;
116 while (state != QAbstractSocket::ConnectedState
117 && (timer.elapsed() < timeLimit))
118 {
119// Windows does not need the scope on the ip address so we can skip
120// some processing
121#ifndef _WIN32
122 int iCardsEnd = 0;
123 if (islinkLocal && !gCoreContext->GetScopeForAddress(addr))
124 {
125 addr.setScopeId(QString());
126 while (addr.scopeId().isEmpty() && iCardsEnd<2)
127 {
128 // search for the next available IPV6 interface.
129 if (iCard.hasNext())
130 {
131 QNetworkInterface card = iCard.next();
132 LOG(VB_GENERAL, LOG_DEBUG, QString("Trying interface %1").arg(card.name()));
133 unsigned int flags = card.flags();
134 if ((flags & QNetworkInterface::IsLoopBack)
135 || !(flags & QNetworkInterface::IsRunning))
136 continue;
137 // check that IPv6 is enabled on that interface
138 QList<QNetworkAddressEntry> addresses = card.addressEntries();
139 bool foundv6 = false;
140 for (const auto& ae : std::as_const(addresses))
141 {
142 if (ae.ip().protocol() == QAbstractSocket::IPv6Protocol)
143 {
144 foundv6 = true;
145 break;
146 }
147 }
148 if (foundv6)
149 {
150 scope = card.name();
151 addr.setScopeId(scope);
152 break;
153 }
154 }
155 else
156 {
157 // Get a new list in case a new interface
158 // has been added.
159 cards = QNetworkInterface::allInterfaces();
160 iCard = cards;
161 iCard.toFront();
162 testedAll=true;
163 iCardsEnd++;
164 }
165 }
166 }
167 if (iCardsEnd > 1)
168 {
169 LOG(VB_GENERAL, LOG_ERR, LOC + QString("There is no IPV6 compatible interface for %1")
170 .arg(host));
171 break;
172 }
173#endif
174 QString dest;
175 if (isIPAddress)
176 dest=addr.toString();
177 else
178 dest=host;
179 socket.connectToHost(dest, port);
180
181 MythTimer attempt_time {MythTimer::kStartRunning};
182 static constexpr std::chrono::milliseconds k_poll_interval {1ms};
183 while (state != QAbstractSocket::ConnectedState
184 && (timer.elapsed() < timeLimit)
185 && attempt_time.elapsed() < 3s
186 )
187 {
188 if (QCoreApplication::instance() != nullptr &&
189 QThread::currentThread() == QCoreApplication::instance()->thread()
190 )
191 {
192 QCoreApplication::processEvents(QEventLoop::AllEvents, k_poll_interval.count());
193 std::this_thread::sleep_for(1ns); // force thread to be de-scheduled
194 }
195 else
196 {
197 std::this_thread::sleep_for(k_poll_interval);
198 }
199 state = socket.state();
200 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("host %1 port %2 socket state %3, attempt time: %4")
201 .arg(host, QString::number(port), QString::number(state),
202 QString::number(attempt_time.elapsed().count())
203 )
204 );
205 // Check if user got impatient and canceled
206 if (m_cancelCheck)
207 break;
208 if (linkLocalOnly && testedAll
209 && state == QAbstractSocket::UnconnectedState
210 && attempt_time.elapsed() > 500ms
211 )
212 break;
213 }
214 socket.abort();
215 // Check if user got impatient and canceled
216 if (m_cancelCheck)
217 break;
218 if (linkLocalOnly
219 && state == QAbstractSocket::UnconnectedState
220 && testedAll)
221 break;
222 }
223 if (state == QAbstractSocket::ConnectedState
224 && islinkLocal && !scope.isEmpty())
225 {
227 host = addr.toString();
228 }
229 return (state == QAbstractSocket::ConnectedState);
230}
231
246// static method
247bool PortChecker::resolveLinkLocal(QString &host, int port, std::chrono::milliseconds timeLimit)
248{
249 PortChecker checker;
250 return checker.checkPort(host,port,timeLimit,true);
251}
252
261{
262 m_cancelCheck = true;
263}
264
265
266/* 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.
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:75
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:40