MythTV  master
portchecker.cpp
Go to the documentation of this file.
1 // 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 <QEventLoop>
29 #include <QNetworkInterface>
30 #include <QNetworkAddressEntry>
31 
32 #include <thread>
33 
34 #include "mythcorecontext.h"
35 #include "mythtimer.h"
36 #include "portchecker.h"
37 
38 #define LOC QString("PortChecker::%1(): ").arg(__func__)
39 
73 bool PortChecker::checkPort(QString &host, int port, int timeLimit, bool linkLocalOnly)
74 {
75  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("host %1 port %2 timeLimit %3 linkLocalOnly %4")
76  .arg(host).arg(port).arg(timeLimit).arg(linkLocalOnly));
77  m_cancelCheck = false;
78  QHostAddress addr;
79  bool isIPAddress = addr.setAddress(host);
80  bool islinkLocal = false;
81 // Windows does not need the scope on the ip address so we can skip
82 // some processing
83 #ifndef _WIN32
84  if (isIPAddress
85  && addr.protocol() == QAbstractSocket::IPv6Protocol
86  && addr.isInSubnet(QHostAddress::parseSubnet("fe80::/10")))
87  islinkLocal = true;
88 #endif
89  if (linkLocalOnly)
90  {
91  if (islinkLocal)
92  {
93  // If we already know the scope, set it here and return
95  {
96  host = addr.toString();
97  return true;
98  }
99  }
100  else
101  return false;
102  }
103  // cppcheck-suppress unreadVariable
104  QList<QNetworkInterface> cards = QNetworkInterface::allInterfaces();
105 #ifndef _WIN32
106  QListIterator<QNetworkInterface> iCard = cards;
107 #endif
109  QTcpSocket socket(this);
110  QAbstractSocket::SocketState state = QAbstractSocket::UnconnectedState;
111  int retryCount = 0;
112  QString scope;
113  bool testedAll = false;
114  while (state != QAbstractSocket::ConnectedState
115  && (timer.elapsed() < timeLimit))
116  {
117  if (state == QAbstractSocket::UnconnectedState)
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  foreach (QNetworkAddressEntry ae, 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  retryCount=0;
181  }
182  else
183  retryCount++;
184  // This retry count of 6 means 3 seconds of waiting for
185  // connection before aborting and starting a new connection attempt.
186  if (retryCount > 6)
187  socket.abort();
188  processEvents();
189  // Check if user got impatient and canceled
190  if (m_cancelCheck)
191  break;
192  std::this_thread::sleep_for(std::chrono::milliseconds(500));
193  state = socket.state();
194  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("socket state %1")
195  .arg(state));
196  if (linkLocalOnly
197  && state == QAbstractSocket::UnconnectedState
198  && testedAll)
199  break;
200  }
201  if (state == QAbstractSocket::ConnectedState
202  && islinkLocal && !scope.isEmpty())
203  {
205  host = addr.toString();
206  }
207  socket.abort();
208  processEvents();
209  return (state == QAbstractSocket::ConnectedState);
210 }
211 
226 // static method
227 bool PortChecker::resolveLinkLocal(QString &host, int port, int timeLimit)
228 {
229  PortChecker checker;
230  return checker.checkPort(host,port,timeLimit,true);
231 }
232 
234 {
235  qApp->processEvents(QEventLoop::AllEvents, 250);
236  qApp->processEvents(QEventLoop::AllEvents, 250);
237 }
238 
247 {
248  m_cancelCheck = true;
249 }
250 
251 
252 /* vim: set expandtab tabstop=4 shiftwidth=4: */
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:13
void cancelPortCheck(void)
Cancel the checkPort operation currently in progress.
#define LOC
Definition: portchecker.cpp:38
void SetScopeForAddress(const QHostAddress &addr)
Record the scope Id of the given IP address.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
bool checkPort(QString &host, int port, int timeLimit=30000, bool linkLocalOnly=false)
Check if a port is open and sort out the link-local scope.
Definition: portchecker.cpp:73
static bool resolveLinkLocal(QString &host, int port, int timeLimit=30000)
Convenience method to resolve link-local address.
bool m_cancelCheck
Definition: portchecker.h:60
Small class to handle TCP port checking and finding link-local context.
Definition: portchecker.h:43
int elapsed(void) const
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:90
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
bool GetScopeForAddress(QHostAddress &addr) const
Return the cached scope Id for the given address.
void processEvents(void)