You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tork/src/upnp/upnpmcastsocket.cpp

314 lines
8.6 KiB

/***************************************************************************
* 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"