File indexing completed on 2024-04-21 03:55:03
0001 /* 0002 SPDX-FileCopyrightText: 2008 Roland Harnau <tau@gmx.eu> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "hostinfo.h" 0008 0009 #include <QCache> 0010 #include <QFutureWatcher> 0011 #include <QHash> 0012 #include <QHostInfo> 0013 #include <QList> 0014 #include <QMetaType> 0015 #include <QPair> 0016 #include <QSemaphore> 0017 #include <QThread> 0018 #include <QTime> 0019 #include <QTimer> 0020 #include <QtConcurrentRun> 0021 0022 #ifdef Q_OS_UNIX 0023 #include <QFileInfo> 0024 #include <arpa/nameser.h> 0025 #include <netinet/in.h> 0026 #include <resolv.h> // for _PATH_RESCONF 0027 #ifndef _PATH_RESCONF 0028 #define _PATH_RESCONF "/etc/resolv.conf" 0029 #endif 0030 #endif 0031 0032 static constexpr int TTL = 300; 0033 0034 namespace KIO 0035 { 0036 class HostInfoAgentPrivate : public QObject 0037 { 0038 Q_OBJECT 0039 0040 class Query; 0041 0042 public: 0043 explicit HostInfoAgentPrivate(int cacheSize = 100); 0044 ~HostInfoAgentPrivate() override 0045 { 0046 } 0047 void lookupHost(const QString &hostName, QObject *receiver, const char *member); 0048 QHostInfo lookupCachedHostInfoFor(const QString &hostName); 0049 void cacheLookup(const QHostInfo &); 0050 private Q_SLOTS: 0051 void queryFinished(const QHostInfo &info, Query *sender); 0052 0053 private: 0054 class Result; 0055 0056 QHash<QString, Query *> openQueries; 0057 struct HostCacheInfo { 0058 QHostInfo hostInfo; 0059 QTime time; 0060 }; 0061 QCache<QString, HostCacheInfo> dnsCache; 0062 QDateTime resolvConfMTime; 0063 }; 0064 0065 class HostInfoAgentPrivate::Result : public QObject 0066 { 0067 Q_OBJECT 0068 Q_SIGNALS: 0069 void result(const QHostInfo &); 0070 0071 private: 0072 friend class HostInfoAgentPrivate; 0073 }; 0074 0075 class HostInfoAgentPrivate::Query : public QObject 0076 { 0077 Q_OBJECT 0078 public: 0079 Query() 0080 : m_watcher() 0081 , m_hostName() 0082 { 0083 connect(&m_watcher, &QFutureWatcher<QHostInfo>::finished, this, &Query::relayFinished); 0084 } 0085 void start(const QString &hostName) 0086 { 0087 m_hostName = hostName; 0088 QFuture<QHostInfo> future = QtConcurrent::run(&QHostInfo::fromName, hostName); 0089 m_watcher.setFuture(future); 0090 } 0091 QString hostName() const 0092 { 0093 return m_hostName; 0094 } 0095 Q_SIGNALS: 0096 void result(const QHostInfo &); 0097 private Q_SLOTS: 0098 void relayFinished() 0099 { 0100 Q_EMIT result(m_watcher.result()); 0101 } 0102 0103 private: 0104 QFutureWatcher<QHostInfo> m_watcher; 0105 QString m_hostName; 0106 }; 0107 0108 class NameLookupThreadRequest 0109 { 0110 public: 0111 NameLookupThreadRequest(const QString &hostName) 0112 : m_hostName(hostName) 0113 { 0114 } 0115 0116 QSemaphore *semaphore() 0117 { 0118 return &m_semaphore; 0119 } 0120 0121 QHostInfo result() const 0122 { 0123 return m_hostInfo; 0124 } 0125 0126 void setResult(const QHostInfo &hostInfo) 0127 { 0128 m_hostInfo = hostInfo; 0129 } 0130 0131 QString hostName() const 0132 { 0133 return m_hostName; 0134 } 0135 0136 int lookupId() const 0137 { 0138 return m_lookupId; 0139 } 0140 0141 void setLookupId(int id) 0142 { 0143 m_lookupId = id; 0144 } 0145 0146 private: 0147 Q_DISABLE_COPY(NameLookupThreadRequest) 0148 QString m_hostName; 0149 QSemaphore m_semaphore; 0150 QHostInfo m_hostInfo; 0151 int m_lookupId; 0152 }; 0153 } 0154 0155 Q_DECLARE_METATYPE(std::shared_ptr<KIO::NameLookupThreadRequest>) 0156 0157 namespace KIO 0158 { 0159 class NameLookUpThreadWorker : public QObject 0160 { 0161 Q_OBJECT 0162 public Q_SLOTS: 0163 void lookupHost(const std::shared_ptr<KIO::NameLookupThreadRequest> &request) 0164 { 0165 const QString hostName = request->hostName(); 0166 const int lookupId = QHostInfo::lookupHost(hostName, this, SLOT(lookupFinished(QHostInfo))); 0167 request->setLookupId(lookupId); 0168 m_lookups.insert(lookupId, request); 0169 } 0170 0171 void abortLookup(const std::shared_ptr<KIO::NameLookupThreadRequest> &request) 0172 { 0173 QHostInfo::abortHostLookup(request->lookupId()); 0174 m_lookups.remove(request->lookupId()); 0175 } 0176 0177 void lookupFinished(const QHostInfo &hostInfo) 0178 { 0179 auto it = m_lookups.find(hostInfo.lookupId()); 0180 if (it != m_lookups.end()) { 0181 (*it)->setResult(hostInfo); 0182 (*it)->semaphore()->release(); 0183 m_lookups.erase(it); 0184 } 0185 } 0186 0187 private: 0188 QMap<int, std::shared_ptr<NameLookupThreadRequest>> m_lookups; 0189 }; 0190 0191 class NameLookUpThread : public QThread 0192 { 0193 Q_OBJECT 0194 public: 0195 NameLookUpThread() 0196 : m_worker(nullptr) 0197 { 0198 qRegisterMetaType<std::shared_ptr<NameLookupThreadRequest>>(); 0199 start(); 0200 } 0201 0202 ~NameLookUpThread() override 0203 { 0204 quit(); 0205 wait(); 0206 } 0207 0208 NameLookUpThreadWorker *worker() 0209 { 0210 return m_worker; 0211 } 0212 0213 QSemaphore *semaphore() 0214 { 0215 return &m_semaphore; 0216 } 0217 0218 void run() override 0219 { 0220 NameLookUpThreadWorker worker; 0221 m_worker = &worker; 0222 m_semaphore.release(); 0223 exec(); 0224 } 0225 0226 private: 0227 NameLookUpThreadWorker *m_worker; 0228 QSemaphore m_semaphore; 0229 }; 0230 } 0231 0232 using namespace KIO; 0233 0234 Q_GLOBAL_STATIC(HostInfoAgentPrivate, hostInfoAgentPrivate) 0235 Q_GLOBAL_STATIC(NameLookUpThread, nameLookUpThread) 0236 0237 void HostInfo::lookupHost(const QString &hostName, QObject *receiver, const char *member) 0238 { 0239 hostInfoAgentPrivate()->lookupHost(hostName, receiver, member); 0240 } 0241 0242 QHostInfo HostInfo::lookupHost(const QString &hostName, unsigned long timeout) 0243 { 0244 // Do not perform a reverse lookup here... 0245 QHostAddress address(hostName); 0246 QHostInfo hostInfo; 0247 if (!address.isNull()) { 0248 QList<QHostAddress> addressList; 0249 addressList << address; 0250 hostInfo.setAddresses(addressList); 0251 return hostInfo; 0252 } 0253 0254 // Look up the name in the KIO DNS cache... 0255 hostInfo = HostInfo::lookupCachedHostInfoFor(hostName); 0256 if (!hostInfo.hostName().isEmpty() && hostInfo.error() == QHostInfo::NoError) { 0257 return hostInfo; 0258 } 0259 0260 // Failing all of the above, do the lookup... 0261 std::shared_ptr<NameLookupThreadRequest> request = std::make_shared<NameLookupThreadRequest>(hostName); 0262 nameLookUpThread()->semaphore()->acquire(); 0263 nameLookUpThread()->semaphore()->release(); 0264 NameLookUpThreadWorker *worker = nameLookUpThread()->worker(); 0265 auto lookupFunc = [worker, request]() { 0266 worker->lookupHost(request); 0267 }; 0268 QMetaObject::invokeMethod(worker, lookupFunc, Qt::QueuedConnection); 0269 if (request->semaphore()->tryAcquire(1, timeout)) { 0270 hostInfo = request->result(); 0271 if (!hostInfo.hostName().isEmpty() && hostInfo.error() == QHostInfo::NoError) { 0272 HostInfo::cacheLookup(hostInfo); // cache the look up... 0273 } 0274 } else { 0275 auto abortFunc = [worker, request]() { 0276 worker->abortLookup(request); 0277 }; 0278 QMetaObject::invokeMethod(worker, abortFunc, Qt::QueuedConnection); 0279 } 0280 0281 // qDebug() << "Name look up succeeded for" << hostName; 0282 return hostInfo; 0283 } 0284 0285 QHostInfo HostInfo::lookupCachedHostInfoFor(const QString &hostName) 0286 { 0287 return hostInfoAgentPrivate()->lookupCachedHostInfoFor(hostName); 0288 } 0289 0290 void HostInfo::cacheLookup(const QHostInfo &info) 0291 { 0292 hostInfoAgentPrivate()->cacheLookup(info); 0293 } 0294 0295 HostInfoAgentPrivate::HostInfoAgentPrivate(int cacheSize) 0296 : openQueries() 0297 , dnsCache(cacheSize) 0298 { 0299 qRegisterMetaType<QHostInfo>(); 0300 } 0301 0302 void HostInfoAgentPrivate::lookupHost(const QString &hostName, QObject *receiver, const char *member) 0303 { 0304 #ifdef _PATH_RESCONF 0305 QFileInfo resolvConf(QFile::decodeName(_PATH_RESCONF)); 0306 QDateTime currentMTime = resolvConf.lastModified(); 0307 if (resolvConf.exists() && currentMTime != resolvConfMTime) { 0308 // /etc/resolv.conf has been modified 0309 // clear our cache 0310 resolvConfMTime = currentMTime; 0311 dnsCache.clear(); 0312 } 0313 #endif 0314 0315 if (HostCacheInfo *info = dnsCache.object(hostName)) { 0316 if (QTime::currentTime() <= info->time.addSecs(TTL)) { 0317 Result result; 0318 if (receiver) { 0319 QObject::connect(&result, SIGNAL(result(QHostInfo)), receiver, member); 0320 Q_EMIT result.result(info->hostInfo); 0321 } 0322 return; 0323 } 0324 dnsCache.remove(hostName); 0325 } 0326 0327 if (Query *query = openQueries.value(hostName)) { 0328 if (receiver) { 0329 connect(query, SIGNAL(result(QHostInfo)), receiver, member); 0330 } 0331 return; 0332 } 0333 0334 Query *query = new Query(); 0335 openQueries.insert(hostName, query); 0336 connect(query, &Query::result, this, [this, query](const QHostInfo &info) { 0337 queryFinished(info, query); 0338 }); 0339 if (receiver) { 0340 connect(query, SIGNAL(result(QHostInfo)), receiver, member); 0341 } 0342 query->start(hostName); 0343 } 0344 0345 QHostInfo HostInfoAgentPrivate::lookupCachedHostInfoFor(const QString &hostName) 0346 { 0347 HostCacheInfo *info = dnsCache.object(hostName); 0348 if (info && info->time.addSecs(TTL) >= QTime::currentTime()) { 0349 return info->hostInfo; 0350 } 0351 0352 // not found in dnsCache 0353 QHostInfo hostInfo; 0354 hostInfo.setError(QHostInfo::HostNotFound); 0355 return hostInfo; 0356 } 0357 0358 void HostInfoAgentPrivate::cacheLookup(const QHostInfo &info) 0359 { 0360 if (info.hostName().isEmpty()) { 0361 return; 0362 } 0363 0364 if (info.error() != QHostInfo::NoError) { 0365 return; 0366 } 0367 0368 dnsCache.insert(info.hostName(), new HostCacheInfo{info, QTime::currentTime()}); 0369 } 0370 0371 void HostInfoAgentPrivate::queryFinished(const QHostInfo &info, Query *sender) 0372 { 0373 const auto host = sender->hostName(); 0374 openQueries.remove(host); 0375 if (info.error() == QHostInfo::NoError) { 0376 dnsCache.insert(host, new HostCacheInfo{info, QTime::currentTime()}); 0377 } 0378 sender->deleteLater(); 0379 } 0380 0381 #include "hostinfo.moc"