File indexing completed on 2025-01-05 04:37:28
0001 /* 0002 SPDX-FileCopyrightText: 2005-2007 Joris Guisson <joris.guisson@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "upnpmcastsocket.h" 0008 #include <QFile> 0009 #include <QStringList> 0010 #include <QTextStream> 0011 #include <QUrl> 0012 0013 #include <netinet/in.h> 0014 #include <sys/socket.h> 0015 #include <unistd.h> 0016 #ifndef Q_WS_WIN 0017 #include <netinet/in_systm.h> 0018 #include <netinet/ip.h> 0019 #endif 0020 #include <arpa/inet.h> 0021 #include <util/log.h> 0022 0023 namespace bt 0024 { 0025 static bool UrlCompare(const QUrl &a, const QUrl &b) 0026 { 0027 if (a == b) 0028 return true; 0029 0030 return a.scheme() == b.scheme() && a.host() == b.host() && a.password() == b.password() && a.port(80) == b.port(80) && a.path() == b.path() 0031 && a.query() == b.query(); // TODO check if ported correctly 0032 } 0033 0034 class UPnPMCastSocket::UPnPMCastSocketPrivate 0035 { 0036 public: 0037 UPnPMCastSocketPrivate(bool verbose); 0038 ~UPnPMCastSocketPrivate(); 0039 0040 UPnPRouter *parseResponse(const QByteArray &arr); 0041 void joinUPnPMCastGroup(int fd); 0042 void leaveUPnPMCastGroup(int fd); 0043 void onXmlFileDownloaded(UPnPRouter *r, bool success); 0044 UPnPRouter *findDevice(const QUrl &location); 0045 0046 QSet<UPnPRouter *> routers; 0047 QSet<UPnPRouter *> pending_routers; // routers which we are downloading the XML file from 0048 bool verbose; 0049 }; 0050 0051 UPnPMCastSocket::UPnPMCastSocket(bool verbose) 0052 : d(new UPnPMCastSocketPrivate(verbose)) 0053 { 0054 QObject::connect(this, &UPnPMCastSocket::readyRead, this, &UPnPMCastSocket::onReadyRead); 0055 QObject::connect(this, &UPnPMCastSocket::errorOccurred, this, &UPnPMCastSocket::error); 0056 0057 for (Uint32 i = 0; i < 10; i++) { 0058 if (!bind(1900 + i, QUdpSocket::ShareAddress)) 0059 Out(SYS_PNP | LOG_IMPORTANT) << "Cannot bind to UDP port 1900 : " << errorString() << endl; 0060 else 0061 break; 0062 } 0063 0064 d->joinUPnPMCastGroup(socketDescriptor()); 0065 } 0066 0067 UPnPMCastSocket::~UPnPMCastSocket() 0068 { 0069 d->leaveUPnPMCastGroup(socketDescriptor()); 0070 delete d; 0071 } 0072 0073 void UPnPMCastSocket::discover() 0074 { 0075 Out(SYS_PNP | LOG_NOTICE) << "Trying to find UPnP devices on the local network" << endl; 0076 0077 // send a HTTP M-SEARCH message to 239.255.255.250:1900 0078 const char *upnp_data = 0079 "M-SEARCH * HTTP/1.1\r\n" 0080 "HOST: 239.255.255.250:1900\r\n" 0081 "ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" 0082 "MAN:\"ssdp:discover\"\r\n" 0083 "MX:3\r\n" 0084 "\r\n\0"; 0085 0086 const char *tr64_data = 0087 "M-SEARCH * HTTP/1.1\r\n" 0088 "HOST: 239.255.255.250:1900\r\n" 0089 "ST:urn:dslforum-org:device:InternetGatewayDevice:1\r\n" 0090 "MAN:\"ssdp:discover\"\r\n" 0091 "MX:3\r\n" 0092 "\r\n\0"; 0093 0094 if (d->verbose) { 0095 Out(SYS_PNP | LOG_NOTICE) << "Sending : " << endl; 0096 Out(SYS_PNP | LOG_NOTICE) << upnp_data << endl; 0097 0098 Out(SYS_PNP | LOG_NOTICE) << "Sending : " << endl; 0099 Out(SYS_PNP | LOG_NOTICE) << tr64_data << endl; 0100 } 0101 0102 writeDatagram(upnp_data, strlen(upnp_data), QHostAddress("239.255.255.250"), 1900); 0103 writeDatagram(tr64_data, strlen(tr64_data), QHostAddress("239.255.255.250"), 1900); 0104 } 0105 0106 void UPnPMCastSocket::onXmlFileDownloaded(UPnPRouter *r, bool success) 0107 { 0108 d->pending_routers.remove(r); 0109 if (!success) { 0110 // we couldn't download and parse the XML file so 0111 // get rid of it 0112 r->deleteLater(); 0113 } else { 0114 // add it to the list and emit the signal 0115 QUrl location = r->getLocation(); 0116 if (d->findDevice(location)) { 0117 r->deleteLater(); 0118 } else { 0119 d->routers.insert(r); 0120 discovered(r); 0121 } 0122 } 0123 } 0124 0125 void UPnPMCastSocket::onReadyRead() 0126 { 0127 if (pendingDatagramSize() == 0) { 0128 Out(SYS_PNP | LOG_NOTICE) << "0 byte UDP packet " << endl; 0129 // KDatagramSocket wrongly handles UDP packets with no payload 0130 // so we need to deal with it oursleves 0131 int fd = socketDescriptor(); 0132 char tmp; 0133 ::read(fd, &tmp, 1); 0134 return; 0135 } 0136 0137 QByteArray data(pendingDatagramSize(), 0); 0138 if (readDatagram(data.data(), pendingDatagramSize()) == -1) 0139 return; 0140 0141 if (d->verbose) { 0142 Out(SYS_PNP | LOG_NOTICE) << "Received : " << endl; 0143 Out(SYS_PNP | LOG_NOTICE) << QString(data) << endl; 0144 } 0145 0146 // try to make a router of it 0147 UPnPRouter *r = d->parseResponse(data); 0148 if (r) { 0149 QObject::connect(r, &UPnPRouter::xmlFileDownloaded, this, &UPnPMCastSocket::onXmlFileDownloaded); 0150 0151 // download it's xml file 0152 r->downloadXMLFile(); 0153 d->pending_routers.insert(r); 0154 } 0155 } 0156 0157 void UPnPMCastSocket::error(QAbstractSocket::SocketError) 0158 { 0159 Out(SYS_PNP | LOG_IMPORTANT) << "UPnPMCastSocket Error : " << errorString() << endl; 0160 } 0161 0162 void UPnPMCastSocket::saveRouters(const QString &file) 0163 { 0164 QFile fptr(file); 0165 if (!fptr.open(QIODevice::WriteOnly)) { 0166 Out(SYS_PNP | LOG_IMPORTANT) << "Cannot open file " << file << " : " << fptr.errorString() << endl; 0167 return; 0168 } 0169 0170 // file format is simple : 2 lines per router, 0171 // one containing the server, the other the location 0172 QTextStream fout(&fptr); 0173 for (UPnPRouter *r : std::as_const(d->routers)) { 0174 fout << r->getServer() << Qt::endl; 0175 fout << r->getLocation().toString() << Qt::endl; 0176 } 0177 } 0178 0179 void UPnPMCastSocket::loadRouters(const QString &file) 0180 { 0181 QFile fptr(file); 0182 if (!fptr.open(QIODevice::ReadOnly)) { 0183 Out(SYS_PNP | LOG_IMPORTANT) << "Cannot open file " << file << " : " << fptr.errorString() << endl; 0184 return; 0185 } 0186 0187 // file format is simple : 2 lines per router, 0188 // one containing the server, the other the location 0189 QTextStream fin(&fptr); 0190 0191 while (!fin.atEnd()) { 0192 QString server, location; 0193 server = fin.readLine(); 0194 location = fin.readLine(); 0195 0196 UPnPRouter *r = new UPnPRouter(server, QUrl(location)); 0197 // download it's xml file 0198 QObject::connect(r, &UPnPRouter::xmlFileDownloaded, this, &UPnPMCastSocket::onXmlFileDownloaded); 0199 r->downloadXMLFile(); 0200 d->pending_routers.insert(r); 0201 } 0202 } 0203 0204 Uint32 UPnPMCastSocket::getNumDevicesDiscovered() const 0205 { 0206 return d->routers.count(); 0207 } 0208 0209 UPnPRouter *UPnPMCastSocket::findDevice(const QString &name) 0210 { 0211 QUrl location(name); 0212 return d->findDevice(location); 0213 } 0214 0215 void UPnPMCastSocket::setVerbose(bool v) 0216 { 0217 d->verbose = v; 0218 } 0219 0220 ///////////////////////////////////////////////////////////// 0221 0222 UPnPMCastSocket::UPnPMCastSocketPrivate::UPnPMCastSocketPrivate(bool verbose) 0223 : verbose(verbose) 0224 { 0225 } 0226 0227 UPnPMCastSocket::UPnPMCastSocketPrivate::~UPnPMCastSocketPrivate() 0228 { 0229 qDeleteAll(pending_routers); 0230 qDeleteAll(routers); 0231 } 0232 0233 void UPnPMCastSocket::UPnPMCastSocketPrivate::joinUPnPMCastGroup(int fd) 0234 { 0235 struct ip_mreq mreq; 0236 memset(&mreq, 0, sizeof(struct ip_mreq)); 0237 0238 inet_aton("239.255.255.250", &mreq.imr_multiaddr); 0239 mreq.imr_interface.s_addr = htonl(INADDR_ANY); 0240 0241 #ifndef Q_WS_WIN 0242 if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(struct ip_mreq)) < 0) 0243 #else 0244 if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(struct ip_mreq)) < 0) 0245 #endif 0246 { 0247 Out(SYS_PNP | LOG_NOTICE) << "Failed to join multicast group 239.255.255.250" << endl; 0248 } 0249 } 0250 0251 void UPnPMCastSocket::UPnPMCastSocketPrivate::leaveUPnPMCastGroup(int fd) 0252 { 0253 struct ip_mreq mreq; 0254 memset(&mreq, 0, sizeof(struct ip_mreq)); 0255 0256 inet_aton("239.255.255.250", &mreq.imr_multiaddr); 0257 mreq.imr_interface.s_addr = htonl(INADDR_ANY); 0258 0259 #ifndef Q_WS_WIN 0260 if (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(struct ip_mreq)) < 0) 0261 #else 0262 if (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char *)&mreq, sizeof(struct ip_mreq)) < 0) 0263 #endif 0264 { 0265 Out(SYS_PNP | LOG_NOTICE) << "Failed to leave multicast group 239.255.255.250" << endl; 0266 } 0267 } 0268 0269 UPnPRouter *UPnPMCastSocket::UPnPMCastSocketPrivate::parseResponse(const QByteArray &arr) 0270 { 0271 const QString response = QString::fromLatin1(arr); 0272 QVector<QStringView> lines = QStringView(response).split(QStringLiteral("\r\n")); 0273 QString server; 0274 QUrl location; 0275 0276 /* 0277 Out(SYS_PNP|LOG_DEBUG) << "Received : " << endl; 0278 for (Uint32 idx = 0;idx < lines.count(); idx++) 0279 Out(SYS_PNP|LOG_DEBUG) << lines[idx] << endl; 0280 */ 0281 0282 // first read first line and see if contains a HTTP 200 OK message 0283 auto line = lines.first(); 0284 if (!line.contains(QLatin1String("HTTP"))) { 0285 // it is either a 200 OK or a NOTIFY 0286 if (!line.contains(QLatin1String("NOTIFY")) && !line.contains(QLatin1String("200"))) 0287 return nullptr; 0288 } else if (line.contains(QLatin1String("M-SEARCH"))) // ignore M-SEARCH 0289 return nullptr; 0290 0291 // quick check that the response being parsed is valid 0292 bool validDevice = false; 0293 for (int idx = 0; idx < lines.count() && !validDevice; idx++) { 0294 line = lines[idx]; 0295 if ((line.contains(QLatin1String("ST:")) || line.contains(QLatin1String("NT:"))) && line.contains(QLatin1String("InternetGatewayDevice"))) { 0296 validDevice = true; 0297 } 0298 } 0299 if (!validDevice) { 0300 // Out(SYS_PNP|LOG_IMPORTANT) << "Not a valid Internet Gateway Device" << endl; 0301 return nullptr; 0302 } 0303 0304 // read all lines and try to find the server and location fields 0305 for (int i = 1; i < lines.count(); i++) { 0306 line = lines[i]; 0307 if (line.startsWith(QLatin1String("location"), Qt::CaseInsensitive)) { 0308 location = QUrl(line.mid(line.indexOf(':') + 1).trimmed().toString()); // TODO fromLocalFile()? 0309 if (!location.isValid()) 0310 return nullptr; 0311 } else if (line.startsWith(QLatin1String("server"), Qt::CaseInsensitive)) { 0312 server = line.mid(line.indexOf(':') + 1).trimmed().toString(); 0313 if (server.length() == 0) 0314 return nullptr; 0315 } 0316 } 0317 0318 if (findDevice(location)) { 0319 return nullptr; 0320 } else { 0321 Out(SYS_PNP | LOG_NOTICE) << "Detected IGD " << server << endl; 0322 // everything OK, make a new UPnPRouter 0323 return new UPnPRouter(server, location, verbose); 0324 } 0325 } 0326 0327 UPnPRouter *UPnPMCastSocket::UPnPMCastSocketPrivate::findDevice(const QUrl &location) 0328 { 0329 for (UPnPRouter *r : std::as_const(routers)) { 0330 if (UrlCompare(r->getLocation(), location)) 0331 return r; 0332 } 0333 0334 return nullptr; 0335 } 0336 0337 } 0338 0339 #include "moc_upnpmcastsocket.cpp"