|
|
|
/***************************************************************************
|
|
|
|
* Copyright (C) 2005 by Joris Guisson *
|
|
|
|
* joris.guisson@gmail.com *
|
|
|
|
* *
|
|
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
|
|
* it under the terms of the GNU General Public License as published by *
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
|
|
* (at your option) any later version. *
|
|
|
|
* *
|
|
|
|
* This program is distributed in the hope that it will be useful, *
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
|
|
* GNU General Public License for more details. *
|
|
|
|
* *
|
|
|
|
* You should have received a copy of the GNU General Public License *
|
|
|
|
* along with this program; if not, write to the *
|
|
|
|
* Free Software Foundation, Inc., *
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
#include <kurl.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <netinet/in_systm.h>
|
|
|
|
#include <netinet/ip.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <netinet/ip.h>
|
|
|
|
#include <ntqstringlist.h>
|
|
|
|
#include <tdesocketdevice.h>
|
|
|
|
#include <tdesocketaddress.h>
|
|
|
|
/*#include <util/log.h>
|
|
|
|
#include <torrent/globals.h>*/
|
|
|
|
#include <ntqfile.h>
|
|
|
|
#include <ntqtextstream.h>
|
|
|
|
#include "upnpmcastsocket.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using namespace KNetwork;
|
|
|
|
using namespace bt;
|
|
|
|
|
|
|
|
namespace kt
|
|
|
|
{
|
|
|
|
|
|
|
|
UPnPMCastSocket::UPnPMCastSocket(bool verbose) : verbose(verbose)
|
|
|
|
{
|
|
|
|
routers.setAutoDelete(true);
|
|
|
|
TQObject::connect(this,SIGNAL(readyRead()),this,SLOT(onReadyRead()));
|
|
|
|
TQObject::connect(this,SIGNAL(gotError(int)),this,SLOT(onError(int)));
|
|
|
|
setAddressReuseable(true);
|
|
|
|
setFamily(KNetwork::KResolver::IPv4Family);
|
|
|
|
setBlocking(true);
|
|
|
|
for (Uint32 i = 0;i < 10;i++)
|
|
|
|
{
|
|
|
|
if (!bind(TQString::null,TQString::number(1900 + i)))
|
|
|
|
kdDebug() << "Cannot bind to UDP port 1900" << endl;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
setBlocking(false);
|
|
|
|
joinUPnPMCastGroup();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UPnPMCastSocket::~UPnPMCastSocket()
|
|
|
|
{
|
|
|
|
leaveUPnPMCastGroup();
|
|
|
|
TQObject::disconnect(this,SIGNAL(readyRead()),this,SLOT(onReadyRead()));
|
|
|
|
TQObject::disconnect(this,SIGNAL(gotError(int)),this,SLOT(onError(int)));
|
|
|
|
}
|
|
|
|
|
|
|
|
void UPnPMCastSocket::discover()
|
|
|
|
{
|
|
|
|
kdDebug() << "Trying to find UPnP devices on the local network" << endl;
|
|
|
|
|
|
|
|
// send a HTTP M-SEARCH message to 239.255.255.250:1900
|
|
|
|
const char* data = "M-SEARCH * HTTP/1.1\r\n"
|
|
|
|
"HOST: 239.255.255.250:1900\r\n"
|
|
|
|
"ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n"
|
|
|
|
"MAN:\"ssdp:discover\"\r\n"
|
|
|
|
"MX:3\r\n"
|
|
|
|
"\r\n\0";
|
|
|
|
|
|
|
|
if (verbose)
|
|
|
|
{
|
|
|
|
kdDebug() << "Sending : " << endl;
|
|
|
|
kdDebug() << data << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
KDatagramSocket::send(KNetwork::KDatagramPacket(data,strlen(data),KInetSocketAddress("239.255.255.250",1900)));
|
|
|
|
}
|
|
|
|
|
|
|
|
void UPnPMCastSocket::onXmlFileDownloaded(UPnPRouter* r,bool success)
|
|
|
|
{
|
|
|
|
if (!success)
|
|
|
|
{
|
|
|
|
// we couldn't download and parse the XML file so
|
|
|
|
// get rid of it
|
|
|
|
r->deleteLater();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// add it to the list and emit the signal
|
|
|
|
if (!routers.contains(r->getServer()))
|
|
|
|
{
|
|
|
|
routers.insert(r->getServer(),r);
|
|
|
|
discovered(r);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
r->deleteLater();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void UPnPMCastSocket::onReadyRead()
|
|
|
|
{
|
|
|
|
if (bytesAvailable() == 0)
|
|
|
|
{
|
|
|
|
kdDebug() << "0 byte UDP packet " << endl;
|
|
|
|
// KDatagramSocket wrongly handles UDP packets with no payload
|
|
|
|
// so we need to deal with it oursleves
|
|
|
|
int fd = socketDevice()->socket();
|
|
|
|
char tmp;
|
|
|
|
read(fd,&tmp,1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
KNetwork::KDatagramPacket p = KDatagramSocket::receive();
|
|
|
|
if (p.isNull())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (verbose)
|
|
|
|
{
|
|
|
|
kdDebug() << "Received : " << endl;
|
|
|
|
kdDebug() << TQString(p.data()) << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
// try to make a router of it
|
|
|
|
UPnPRouter* r = parseResponse(p.data());
|
|
|
|
if (r)
|
|
|
|
{
|
|
|
|
TQObject::connect(r,SIGNAL(xmlFileDownloaded( UPnPRouter*, bool )),
|
|
|
|
this,SLOT(onXmlFileDownloaded( UPnPRouter*, bool )));
|
|
|
|
|
|
|
|
// download it's xml file
|
|
|
|
r->downloadXMLFile();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UPnPRouter* UPnPMCastSocket::parseResponse(const TQByteArray & arr)
|
|
|
|
{
|
|
|
|
TQStringList lines = TQStringList::split("\r\n",TQString(arr),false);
|
|
|
|
TQString server;
|
|
|
|
KURL location;
|
|
|
|
|
|
|
|
|
|
|
|
kdDebug() << "Received : " << endl;
|
|
|
|
for (Uint32 idx = 0;idx < lines.count(); idx++)
|
|
|
|
kdDebug() << lines[idx] << endl;
|
|
|
|
|
|
|
|
|
|
|
|
// first read first line and see if contains a HTTP 200 OK message
|
|
|
|
TQString line = lines.first();
|
|
|
|
if (!line.contains("HTTP"))
|
|
|
|
{
|
|
|
|
// it is either a 200 OK or a NOTIFY
|
|
|
|
if (!line.contains("NOTIFY") && !line.contains("200"))
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else if (line.contains("M-SEARCH")) // ignore M-SEARCH
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// quick check that the response being parsed is valid
|
|
|
|
bool validDevice = false;
|
|
|
|
for (Uint32 idx = 0;idx < lines.count() && !validDevice; idx++)
|
|
|
|
{
|
|
|
|
line = lines[idx];
|
|
|
|
if ((line.contains("ST:") || line.contains("NT:")) && line.contains("InternetGatewayDevice"))
|
|
|
|
{
|
|
|
|
validDevice = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!validDevice)
|
|
|
|
{
|
|
|
|
kdDebug() << "Not a valid Internet Gateway Device" << endl;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// read all lines and try to find the server and location fields
|
|
|
|
for (Uint32 i = 1;i < lines.count();i++)
|
|
|
|
{
|
|
|
|
line = lines[i];
|
|
|
|
if (line.startsWith("Location") || line.startsWith("LOCATION") || line.startsWith("location"))
|
|
|
|
{
|
|
|
|
location = line.mid(line.find(':') + 1).stripWhiteSpace();
|
|
|
|
if (!location.isValid())
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else if (line.startsWith("Server") || line.startsWith("server") || line.startsWith("SERVER"))
|
|
|
|
{
|
|
|
|
server = line.mid(line.find(':') + 1).stripWhiteSpace();
|
|
|
|
if (server.length() == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (routers.contains(server))
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
kdDebug() << "Detected IGD " << server << endl;
|
|
|
|
// everything OK, make a new UPnPRouter
|
|
|
|
return new UPnPRouter(server,location,verbose);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void UPnPMCastSocket::onError(int)
|
|
|
|
{
|
|
|
|
kdDebug() << "UPnPMCastSocket Error : " << errorString() << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
void UPnPMCastSocket::saveRouters(const TQString & file)
|
|
|
|
{
|
|
|
|
TQFile fptr(file);
|
|
|
|
if (!fptr.open(IO_WriteOnly))
|
|
|
|
{
|
|
|
|
kdDebug() << "Cannot open file " << file << " : " << fptr.errorString() << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// file format is simple : 2 lines per router,
|
|
|
|
// one containing the server, the other the location
|
|
|
|
TQTextStream fout(&fptr);
|
|
|
|
bt::PtrMap<TQString,UPnPRouter>::iterator i = routers.begin();
|
|
|
|
while (i != routers.end())
|
|
|
|
{
|
|
|
|
UPnPRouter* r = i->second;
|
|
|
|
fout << r->getServer() << endl;
|
|
|
|
fout << r->getLocation().prettyURL() << endl;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void UPnPMCastSocket::loadRouters(const TQString & file)
|
|
|
|
{
|
|
|
|
TQFile fptr(file);
|
|
|
|
if (!fptr.open(IO_ReadOnly))
|
|
|
|
{
|
|
|
|
kdDebug() << "Cannot open file " << file << " : " << fptr.errorString() << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// file format is simple : 2 lines per router,
|
|
|
|
// one containing the server, the other the location
|
|
|
|
TQTextStream fin(&fptr);
|
|
|
|
|
|
|
|
while (!fin.atEnd())
|
|
|
|
{
|
|
|
|
TQString server, location;
|
|
|
|
server = fin.readLine();
|
|
|
|
location = fin.readLine();
|
|
|
|
if (!routers.contains(server))
|
|
|
|
{
|
|
|
|
UPnPRouter* r = new UPnPRouter(server,location);
|
|
|
|
// download it's xml file
|
|
|
|
TQObject::connect(r,SIGNAL(xmlFileDownloaded( UPnPRouter*, bool )),this,SLOT(onXmlFileDownloaded( UPnPRouter*, bool )));
|
|
|
|
r->downloadXMLFile();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void UPnPMCastSocket::joinUPnPMCastGroup()
|
|
|
|
{
|
|
|
|
int fd = socketDevice()->socket();
|
|
|
|
struct ip_mreq mreq;
|
|
|
|
|
|
|
|
memset(&mreq,0,sizeof(struct ip_mreq));
|
|
|
|
|
|
|
|
inet_aton("239.255.255.250",&mreq.imr_multiaddr);
|
|
|
|
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
|
|
|
|
|
|
|
|
if (setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(struct ip_mreq)) < 0)
|
|
|
|
{
|
|
|
|
kdDebug() << "Failed to join multicast group 239.255.255.250" << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void UPnPMCastSocket::leaveUPnPMCastGroup()
|
|
|
|
{
|
|
|
|
int fd = socketDevice()->socket();
|
|
|
|
struct ip_mreq mreq;
|
|
|
|
|
|
|
|
memset(&mreq,0,sizeof(struct ip_mreq));
|
|
|
|
|
|
|
|
inet_aton("239.255.255.250",&mreq.imr_multiaddr);
|
|
|
|
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
|
|
|
|
|
|
|
|
if (setsockopt(fd,IPPROTO_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(struct ip_mreq)) < 0)
|
|
|
|
{
|
|
|
|
kdDebug() << "Failed to leave multicast group 239.255.255.250" << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "upnpmcastsocket.moc"
|