1292 lines
28 KiB
C++
1292 lines
28 KiB
C++
|
|
/*
|
||
|
|
* Copyright (C) 2006 Justin Karneges
|
||
|
|
*
|
||
|
|
* This library is free software; you can redistribute it and/or
|
||
|
|
* modify it under the terms of the GNU Lesser General Public
|
||
|
|
* License as published by the Free Software Foundation; either
|
||
|
|
* version 2.1 of the License, or (at your option) any later version.
|
||
|
|
*
|
||
|
|
* This library 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
|
||
|
|
* Lesser General Public License for more details.
|
||
|
|
*
|
||
|
|
* You should have received a copy of the GNU Lesser General Public
|
||
|
|
* License along with this library; if not, write to the Free Software
|
||
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||
|
|
* 02110-1301 USA
|
||
|
|
*
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <iris/processquit.h>
|
||
|
|
#include <iris/netinterface.h>
|
||
|
|
#include <iris/netavailability.h>
|
||
|
|
#include <iris/netnames.h>
|
||
|
|
#include <iris/addressresolver.h>
|
||
|
|
#include <iris/stunmessage.h>
|
||
|
|
#include <iris/stuntransaction.h>
|
||
|
|
#include <iris/stunbinding.h>
|
||
|
|
#include <iris/stunallocate.h>
|
||
|
|
#include <iris/turnclient.h>
|
||
|
|
#include <QtCrypto>
|
||
|
|
|
||
|
|
using namespace XMPP;
|
||
|
|
|
||
|
|
static QString prompt(const QString &s)
|
||
|
|
{
|
||
|
|
printf("* %s ", qPrintable(s));
|
||
|
|
fflush(stdout);
|
||
|
|
char line[256];
|
||
|
|
fgets(line, 255, stdin);
|
||
|
|
QString result = line;
|
||
|
|
if(result[result.length()-1] == '\n')
|
||
|
|
result.truncate(result.length()-1);
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
class NetMonitor : public QObject
|
||
|
|
{
|
||
|
|
Q_OBJECT
|
||
|
|
public:
|
||
|
|
NetInterfaceManager *man;
|
||
|
|
QList<NetInterface*> ifaces;
|
||
|
|
NetAvailability *netavail;
|
||
|
|
|
||
|
|
~NetMonitor()
|
||
|
|
{
|
||
|
|
delete netavail;
|
||
|
|
qDeleteAll(ifaces);
|
||
|
|
delete man;
|
||
|
|
}
|
||
|
|
|
||
|
|
signals:
|
||
|
|
void quit();
|
||
|
|
|
||
|
|
public slots:
|
||
|
|
void start()
|
||
|
|
{
|
||
|
|
connect(ProcessQuit::instance(), SIGNAL(quit()), SIGNAL(quit()));
|
||
|
|
|
||
|
|
man = new NetInterfaceManager;
|
||
|
|
connect(man, SIGNAL(interfaceAvailable(const QString &)),
|
||
|
|
SLOT(here(const QString &)));
|
||
|
|
QStringList list = man->interfaces();
|
||
|
|
for(int n = 0; n < list.count(); ++n)
|
||
|
|
here(list[n]);
|
||
|
|
|
||
|
|
netavail = new NetAvailability;
|
||
|
|
connect(netavail, SIGNAL(changed(bool)), SLOT(avail(bool)));
|
||
|
|
avail(netavail->isAvailable());
|
||
|
|
}
|
||
|
|
|
||
|
|
void here(const QString &id)
|
||
|
|
{
|
||
|
|
NetInterface *iface = new NetInterface(id, man);
|
||
|
|
connect(iface, SIGNAL(unavailable()), SLOT(gone()));
|
||
|
|
printf("HERE: %s name=[%s]\n", qPrintable(iface->id()), qPrintable(iface->name()));
|
||
|
|
QList<QHostAddress> addrs = iface->addresses();
|
||
|
|
for(int n = 0; n < addrs.count(); ++n)
|
||
|
|
printf(" address: %s\n", qPrintable(addrs[n].toString()));
|
||
|
|
if(!iface->gateway().isNull())
|
||
|
|
printf(" gateway: %s\n", qPrintable(iface->gateway().toString()));
|
||
|
|
ifaces += iface;
|
||
|
|
}
|
||
|
|
|
||
|
|
void gone()
|
||
|
|
{
|
||
|
|
NetInterface *iface = (NetInterface *)sender();
|
||
|
|
printf("GONE: %s\n", qPrintable(iface->id()));
|
||
|
|
ifaces.removeAll(iface);
|
||
|
|
delete iface;
|
||
|
|
}
|
||
|
|
|
||
|
|
void avail(bool available)
|
||
|
|
{
|
||
|
|
if(available)
|
||
|
|
printf("** Network available\n");
|
||
|
|
else
|
||
|
|
printf("** Network unavailable\n");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
static QString dataToString(const QByteArray &buf)
|
||
|
|
{
|
||
|
|
QString out;
|
||
|
|
for(int n = 0; n < buf.size(); ++n)
|
||
|
|
{
|
||
|
|
unsigned char c = (unsigned char)buf[n];
|
||
|
|
if(c == '\\')
|
||
|
|
out += "\\\\";
|
||
|
|
else if(c >= 0x20 || c < 0x7f)
|
||
|
|
out += c;
|
||
|
|
else
|
||
|
|
out += QString("\\x%1").arg((uint)c, 2, 16);
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void print_record(const NameRecord &r)
|
||
|
|
{
|
||
|
|
switch(r.type())
|
||
|
|
{
|
||
|
|
case NameRecord::A:
|
||
|
|
printf("A: [%s] (ttl=%d)\n", qPrintable(r.address().toString()), r.ttl());
|
||
|
|
break;
|
||
|
|
case NameRecord::Aaaa:
|
||
|
|
printf("AAAA: [%s] (ttl=%d)\n", qPrintable(r.address().toString()), r.ttl());
|
||
|
|
break;
|
||
|
|
case NameRecord::Mx:
|
||
|
|
printf("MX: [%s] priority=%d (ttl=%d)\n", r.name().data(), r.priority(), r.ttl());
|
||
|
|
break;
|
||
|
|
case NameRecord::Srv:
|
||
|
|
printf("SRV: [%s] port=%d priority=%d weight=%d (ttl=%d)\n", r.name().data(), r.port(), r.priority(), r.weight(), r.ttl());
|
||
|
|
break;
|
||
|
|
case NameRecord::Ptr:
|
||
|
|
printf("PTR: [%s] (ttl=%d)\n", r.name().data(), r.ttl());
|
||
|
|
break;
|
||
|
|
case NameRecord::Txt:
|
||
|
|
{
|
||
|
|
QList<QByteArray> texts = r.texts();
|
||
|
|
printf("TXT: count=%d (ttl=%d)\n", texts.count(), r.ttl());
|
||
|
|
for(int n = 0; n < texts.count(); ++n)
|
||
|
|
printf(" len=%d [%s]\n", texts[n].size(), qPrintable(dataToString(texts[n])));
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case NameRecord::Hinfo:
|
||
|
|
printf("HINFO: [%s] [%s] (ttl=%d)\n", r.cpu().data(), r.os().data(), r.ttl());
|
||
|
|
break;
|
||
|
|
case NameRecord::Null:
|
||
|
|
printf("NULL: %d bytes (ttl=%d)\n", r.rawData().size(), r.ttl());
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
printf("(Unknown): type=%d (ttl=%d)\n", r.type(), r.ttl());
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static int str2rtype(const QString &in)
|
||
|
|
{
|
||
|
|
QString str = in.toLower();
|
||
|
|
if(str == "a")
|
||
|
|
return NameRecord::A;
|
||
|
|
else if(str == "aaaa")
|
||
|
|
return NameRecord::Aaaa;
|
||
|
|
else if(str == "ptr")
|
||
|
|
return NameRecord::Ptr;
|
||
|
|
else if(str == "srv")
|
||
|
|
return NameRecord::Srv;
|
||
|
|
else if(str == "mx")
|
||
|
|
return NameRecord::Mx;
|
||
|
|
else if(str == "txt")
|
||
|
|
return NameRecord::Txt;
|
||
|
|
else if(str == "hinfo")
|
||
|
|
return NameRecord::Hinfo;
|
||
|
|
else if(str == "null")
|
||
|
|
return NameRecord::Null;
|
||
|
|
else
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
class ResolveName : public QObject
|
||
|
|
{
|
||
|
|
Q_OBJECT
|
||
|
|
public:
|
||
|
|
QString name;
|
||
|
|
NameRecord::Type type;
|
||
|
|
bool longlived;
|
||
|
|
NameResolver dns;
|
||
|
|
bool null_dump;
|
||
|
|
|
||
|
|
ResolveName()
|
||
|
|
{
|
||
|
|
null_dump = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public slots:
|
||
|
|
void start()
|
||
|
|
{
|
||
|
|
connect(ProcessQuit::instance(), SIGNAL(quit()), SIGNAL(quit()));
|
||
|
|
|
||
|
|
connect(&dns, SIGNAL(resultsReady(const QList<XMPP::NameRecord> &)),
|
||
|
|
SLOT(dns_resultsReady(const QList<XMPP::NameRecord> &)));
|
||
|
|
connect(&dns, SIGNAL(error(XMPP::NameResolver::Error)),
|
||
|
|
SLOT(dns_error(XMPP::NameResolver::Error)));
|
||
|
|
|
||
|
|
dns.start(name.toLatin1(), type, longlived ? NameResolver::LongLived : NameResolver::Single);
|
||
|
|
}
|
||
|
|
|
||
|
|
signals:
|
||
|
|
void quit();
|
||
|
|
|
||
|
|
private slots:
|
||
|
|
void dns_resultsReady(const QList<XMPP::NameRecord> &list)
|
||
|
|
{
|
||
|
|
if(null_dump && list[0].type() == NameRecord::Null)
|
||
|
|
{
|
||
|
|
QByteArray buf = list[0].rawData();
|
||
|
|
if(fwrite(buf.data(), buf.size(), 1, stdout) < 1)
|
||
|
|
{
|
||
|
|
fprintf(stderr, "Error: unable to write raw record to stdout\n");
|
||
|
|
emit quit();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
for(int n = 0; n < list.count(); ++n)
|
||
|
|
print_record(list[n]);
|
||
|
|
}
|
||
|
|
if(!longlived)
|
||
|
|
{
|
||
|
|
dns.stop();
|
||
|
|
emit quit();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void dns_error(XMPP::NameResolver::Error e)
|
||
|
|
{
|
||
|
|
QString str;
|
||
|
|
if(e == NameResolver::ErrorNoName)
|
||
|
|
str = "ErrorNoName";
|
||
|
|
else if(e == NameResolver::ErrorTimeout)
|
||
|
|
str = "ErrorTimeout";
|
||
|
|
else if(e == NameResolver::ErrorNoLocal)
|
||
|
|
str = "ErrorNoLocal";
|
||
|
|
else if(e == NameResolver::ErrorNoLongLived)
|
||
|
|
str = "ErrorNoLongLived";
|
||
|
|
else // ErrorGeneric, or anything else
|
||
|
|
str = "ErrorGeneric";
|
||
|
|
|
||
|
|
printf("Error: %s\n", qPrintable(str));
|
||
|
|
emit quit();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
class ResolveAddr : public QObject
|
||
|
|
{
|
||
|
|
Q_OBJECT
|
||
|
|
public:
|
||
|
|
QString name;
|
||
|
|
AddressResolver dns;
|
||
|
|
|
||
|
|
public slots:
|
||
|
|
void start()
|
||
|
|
{
|
||
|
|
connect(ProcessQuit::instance(), SIGNAL(quit()), SIGNAL(quit()));
|
||
|
|
|
||
|
|
connect(&dns, SIGNAL(resultsReady(const QList<QHostAddress> &)),
|
||
|
|
SLOT(dns_resultsReady(const QList<QHostAddress> &)));
|
||
|
|
connect(&dns, SIGNAL(error(XMPP::AddressResolver::Error)),
|
||
|
|
SLOT(dns_error(XMPP::AddressResolver::Error)));
|
||
|
|
|
||
|
|
dns.start(name.toLatin1());
|
||
|
|
}
|
||
|
|
|
||
|
|
signals:
|
||
|
|
void quit();
|
||
|
|
|
||
|
|
private slots:
|
||
|
|
void dns_resultsReady(const QList<QHostAddress> &list)
|
||
|
|
{
|
||
|
|
for(int n = 0; n < list.count(); ++n)
|
||
|
|
printf("%s\n", qPrintable(list[n].toString()));
|
||
|
|
|
||
|
|
emit quit();
|
||
|
|
}
|
||
|
|
|
||
|
|
void dns_error(XMPP::AddressResolver::Error e)
|
||
|
|
{
|
||
|
|
Q_UNUSED(e);
|
||
|
|
|
||
|
|
QString str;
|
||
|
|
//else // ErrorGeneric, or anything else
|
||
|
|
str = "ErrorGeneric";
|
||
|
|
|
||
|
|
printf("Error: %s\n", qPrintable(str));
|
||
|
|
emit quit();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
class BrowseServices : public QObject
|
||
|
|
{
|
||
|
|
Q_OBJECT
|
||
|
|
public:
|
||
|
|
QString type, domain;
|
||
|
|
ServiceBrowser browser;
|
||
|
|
|
||
|
|
public slots:
|
||
|
|
void start()
|
||
|
|
{
|
||
|
|
connect(ProcessQuit::instance(), SIGNAL(quit()), SIGNAL(quit()));
|
||
|
|
|
||
|
|
connect(&browser, SIGNAL(instanceAvailable(const XMPP::ServiceInstance &)),
|
||
|
|
SLOT(browser_instanceAvailable(const XMPP::ServiceInstance &)));
|
||
|
|
connect(&browser, SIGNAL(instanceUnavailable(const XMPP::ServiceInstance &)),
|
||
|
|
SLOT(browser_instanceUnavailable(const XMPP::ServiceInstance &)));
|
||
|
|
connect(&browser, SIGNAL(error()), SLOT(browser_error()));
|
||
|
|
|
||
|
|
browser.start(type, domain);
|
||
|
|
}
|
||
|
|
|
||
|
|
signals:
|
||
|
|
void quit();
|
||
|
|
|
||
|
|
private slots:
|
||
|
|
void browser_instanceAvailable(const XMPP::ServiceInstance &i)
|
||
|
|
{
|
||
|
|
printf("HERE: [%s] (%d attributes)\n", qPrintable(i.instance()), i.attributes().count());
|
||
|
|
QMap<QString,QByteArray> attribs = i.attributes();
|
||
|
|
QMapIterator<QString,QByteArray> it(attribs);
|
||
|
|
while(it.hasNext())
|
||
|
|
{
|
||
|
|
it.next();
|
||
|
|
printf(" [%s] = [%s]\n", qPrintable(it.key()), qPrintable(dataToString(it.value())));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void browser_instanceUnavailable(const XMPP::ServiceInstance &i)
|
||
|
|
{
|
||
|
|
printf("GONE: [%s]\n", qPrintable(i.instance()));
|
||
|
|
}
|
||
|
|
|
||
|
|
void browser_error()
|
||
|
|
{
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
class ResolveService : public QObject
|
||
|
|
{
|
||
|
|
Q_OBJECT
|
||
|
|
public:
|
||
|
|
int mode;
|
||
|
|
QString instance;
|
||
|
|
QString type;
|
||
|
|
QString domain;
|
||
|
|
int port;
|
||
|
|
|
||
|
|
ServiceResolver dns;
|
||
|
|
|
||
|
|
public slots:
|
||
|
|
void start()
|
||
|
|
{
|
||
|
|
connect(ProcessQuit::instance(), SIGNAL(quit()), SIGNAL(quit()));
|
||
|
|
|
||
|
|
connect(&dns, SIGNAL(resultsReady(const QHostAddress &, int)),
|
||
|
|
SLOT(dns_resultsReady(const QHostAddress &, int)));
|
||
|
|
connect(&dns, SIGNAL(finished()), SLOT(dns_finished()));
|
||
|
|
connect(&dns, SIGNAL(error()), SLOT(dns_error()));
|
||
|
|
|
||
|
|
if(mode == 0)
|
||
|
|
dns.startFromInstance(instance.toLatin1() + '.' + type.toLatin1() + ".local.");
|
||
|
|
else if(mode == 1)
|
||
|
|
dns.startFromDomain(domain, type);
|
||
|
|
else // 2
|
||
|
|
dns.startFromPlain(domain, port);
|
||
|
|
}
|
||
|
|
|
||
|
|
signals:
|
||
|
|
void quit();
|
||
|
|
|
||
|
|
private slots:
|
||
|
|
void dns_resultsReady(const QHostAddress &addr, int port)
|
||
|
|
{
|
||
|
|
printf("[%s] port=%d\n", qPrintable(addr.toString()), port);
|
||
|
|
dns.tryNext();
|
||
|
|
}
|
||
|
|
|
||
|
|
void dns_finished()
|
||
|
|
{
|
||
|
|
emit quit();
|
||
|
|
}
|
||
|
|
|
||
|
|
void dns_error()
|
||
|
|
{
|
||
|
|
printf("Error\n");
|
||
|
|
emit quit();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
class PublishService : public QObject
|
||
|
|
{
|
||
|
|
Q_OBJECT
|
||
|
|
public:
|
||
|
|
QString instance;
|
||
|
|
QString type;
|
||
|
|
int port;
|
||
|
|
QMap<QString,QByteArray> attribs;
|
||
|
|
QByteArray extra_null;
|
||
|
|
|
||
|
|
ServiceLocalPublisher pub;
|
||
|
|
|
||
|
|
public slots:
|
||
|
|
void start()
|
||
|
|
{
|
||
|
|
//NetInterfaceManager::instance();
|
||
|
|
|
||
|
|
connect(ProcessQuit::instance(), SIGNAL(quit()), SIGNAL(quit()));
|
||
|
|
|
||
|
|
connect(&pub, SIGNAL(published()), SLOT(pub_published()));
|
||
|
|
connect(&pub, SIGNAL(error(XMPP::ServiceLocalPublisher::Error)),
|
||
|
|
SLOT(pub_error(XMPP::ServiceLocalPublisher::Error)));
|
||
|
|
|
||
|
|
pub.publish(instance, type, port, attribs);
|
||
|
|
}
|
||
|
|
|
||
|
|
signals:
|
||
|
|
void quit();
|
||
|
|
|
||
|
|
private slots:
|
||
|
|
void pub_published()
|
||
|
|
{
|
||
|
|
printf("Published\n");
|
||
|
|
if(!extra_null.isEmpty())
|
||
|
|
{
|
||
|
|
NameRecord rec;
|
||
|
|
rec.setNull(extra_null);
|
||
|
|
pub.addRecord(rec);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void pub_error(XMPP::ServiceLocalPublisher::Error e)
|
||
|
|
{
|
||
|
|
printf("Error: [%d]\n", e);
|
||
|
|
emit quit();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
class StunBind : public QObject
|
||
|
|
{
|
||
|
|
Q_OBJECT
|
||
|
|
public:
|
||
|
|
bool debug;
|
||
|
|
QHostAddress addr;
|
||
|
|
int port;
|
||
|
|
int localPort;
|
||
|
|
QUdpSocket *sock;
|
||
|
|
StunTransactionPool *pool;
|
||
|
|
StunBinding *binding;
|
||
|
|
|
||
|
|
~StunBind()
|
||
|
|
{
|
||
|
|
// make sure transactions are always deleted before the pool
|
||
|
|
delete binding;
|
||
|
|
}
|
||
|
|
|
||
|
|
public slots:
|
||
|
|
void start()
|
||
|
|
{
|
||
|
|
sock = new QUdpSocket(this);
|
||
|
|
connect(sock, SIGNAL(readyRead()), SLOT(sock_readyRead()));
|
||
|
|
|
||
|
|
pool = new StunTransactionPool(StunTransaction::Udp, this);
|
||
|
|
if(debug)
|
||
|
|
pool->setDebugLevel(StunTransactionPool::DL_Packet);
|
||
|
|
else
|
||
|
|
pool->setDebugLevel(StunTransactionPool::DL_Info);
|
||
|
|
connect(pool, SIGNAL(outgoingMessage(const QByteArray &, const QHostAddress &, int)), SLOT(pool_outgoingMessage(const QByteArray &, const QHostAddress &, int)));
|
||
|
|
connect(pool, SIGNAL(debugLine(const QString &)), SLOT(pool_debugLine(const QString &)));
|
||
|
|
|
||
|
|
if(!sock->bind(localPort != -1 ? localPort : 0))
|
||
|
|
{
|
||
|
|
printf("Error binding to local port.\n");
|
||
|
|
emit quit();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
printf("Bound to local port %d.\n", sock->localPort());
|
||
|
|
|
||
|
|
binding = new StunBinding(pool);
|
||
|
|
connect(binding, SIGNAL(success()), SLOT(binding_success()));
|
||
|
|
connect(binding, SIGNAL(error(XMPP::StunBinding::Error)), SLOT(binding_error(XMPP::StunBinding::Error)));
|
||
|
|
binding->start();
|
||
|
|
}
|
||
|
|
|
||
|
|
signals:
|
||
|
|
void quit();
|
||
|
|
|
||
|
|
private slots:
|
||
|
|
void sock_readyRead()
|
||
|
|
{
|
||
|
|
while(sock->hasPendingDatagrams())
|
||
|
|
{
|
||
|
|
QByteArray buf(sock->pendingDatagramSize(), 0);
|
||
|
|
QHostAddress from;
|
||
|
|
quint16 fromPort;
|
||
|
|
|
||
|
|
sock->readDatagram(buf.data(), buf.size(), &from, &fromPort);
|
||
|
|
if(from == addr && fromPort == port)
|
||
|
|
{
|
||
|
|
processDatagram(buf);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
printf("Response from unknown sender %s:%d, dropping.\n", qPrintable(from.toString()), fromPort);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void pool_outgoingMessage(const QByteArray &packet, const QHostAddress &toAddress, int toPort)
|
||
|
|
{
|
||
|
|
// in this example, we aren't using IP-associated transactions
|
||
|
|
Q_UNUSED(toAddress);
|
||
|
|
Q_UNUSED(toPort);
|
||
|
|
|
||
|
|
sock->writeDatagram(packet, addr, port);
|
||
|
|
}
|
||
|
|
|
||
|
|
void pool_debugLine(const QString &line)
|
||
|
|
{
|
||
|
|
printf("%s\n", qPrintable(line));
|
||
|
|
}
|
||
|
|
|
||
|
|
void binding_success()
|
||
|
|
{
|
||
|
|
QHostAddress saddr = binding->reflexiveAddress();
|
||
|
|
quint16 sport = binding->reflexivePort();
|
||
|
|
printf("Server says we are %s;%d\n", qPrintable(saddr.toString()), sport);
|
||
|
|
emit quit();
|
||
|
|
}
|
||
|
|
|
||
|
|
void binding_error(XMPP::StunBinding::Error e)
|
||
|
|
{
|
||
|
|
Q_UNUSED(e);
|
||
|
|
printf("Error: %s\n", qPrintable(binding->errorString()));
|
||
|
|
emit quit();
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
void processDatagram(const QByteArray &buf)
|
||
|
|
{
|
||
|
|
StunMessage message = StunMessage::fromBinary(buf);
|
||
|
|
if(message.isNull())
|
||
|
|
{
|
||
|
|
printf("Warning: server responded with what doesn't seem to be a STUN packet, skipping.\n");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(!pool->writeIncomingMessage(message))
|
||
|
|
printf("Warning: received unexpected message, skipping.\n");
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
class TurnClientTest : public QObject
|
||
|
|
{
|
||
|
|
Q_OBJECT
|
||
|
|
public:
|
||
|
|
int mode;
|
||
|
|
bool debug;
|
||
|
|
QHostAddress relayAddr;
|
||
|
|
int relayPort;
|
||
|
|
QString relayUser, relayPass, relayRealm;
|
||
|
|
QHostAddress peerAddr;
|
||
|
|
int peerPort;
|
||
|
|
QUdpSocket *udp;
|
||
|
|
StunTransactionPool *pool;
|
||
|
|
QList<bool> writeItems; // true = turn-originated, false = external
|
||
|
|
TurnClient *turn;
|
||
|
|
|
||
|
|
TurnClientTest() :
|
||
|
|
udp(0),
|
||
|
|
pool(0),
|
||
|
|
turn(0)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
~TurnClientTest()
|
||
|
|
{
|
||
|
|
// make sure transactions are always deleted before the pool
|
||
|
|
delete turn;
|
||
|
|
}
|
||
|
|
|
||
|
|
public slots:
|
||
|
|
void start()
|
||
|
|
{
|
||
|
|
connect(ProcessQuit::instance(), SIGNAL(quit()), SLOT(do_quit()));
|
||
|
|
|
||
|
|
turn = new TurnClient(this);
|
||
|
|
if(debug)
|
||
|
|
turn->setDebugLevel(TurnClient::DL_Packet);
|
||
|
|
else
|
||
|
|
turn->setDebugLevel(TurnClient::DL_Info);
|
||
|
|
|
||
|
|
connect(turn, SIGNAL(connected()), SLOT(turn_connected()));
|
||
|
|
connect(turn, SIGNAL(tlsHandshaken()), SLOT(turn_tlsHandshaken()));
|
||
|
|
connect(turn, SIGNAL(closed()), SLOT(turn_closed()));
|
||
|
|
connect(turn, SIGNAL(needAuthParams()), SLOT(turn_needAuthParams()));
|
||
|
|
connect(turn, SIGNAL(retrying()), SLOT(turn_retrying()));
|
||
|
|
connect(turn, SIGNAL(activated()), SLOT(turn_activated()));
|
||
|
|
connect(turn, SIGNAL(readyRead()), SLOT(turn_readyRead()));
|
||
|
|
connect(turn, SIGNAL(packetsWritten(int, const QHostAddress &, int)), SLOT(turn_packetsWritten(int, const QHostAddress &, int)));
|
||
|
|
connect(turn, SIGNAL(error(XMPP::TurnClient::Error)), SLOT(turn_error(XMPP::TurnClient::Error)));
|
||
|
|
connect(turn, SIGNAL(outgoingDatagram(const QByteArray &)), SLOT(turn_outgoingDatagram(const QByteArray &)));
|
||
|
|
connect(turn, SIGNAL(debugLine(const QString &)), SLOT(turn_debugLine(const QString &)));
|
||
|
|
|
||
|
|
turn->setClientSoftwareNameAndVersion("nettool (Iris)");
|
||
|
|
|
||
|
|
if(mode == 0)
|
||
|
|
{
|
||
|
|
udp = new QUdpSocket(this);
|
||
|
|
connect(udp, SIGNAL(readyRead()), SLOT(udp_readyRead()));
|
||
|
|
|
||
|
|
// QUdpSocket bytesWritten is not DOR-DS safe, so we queue
|
||
|
|
connect(udp, SIGNAL(bytesWritten(qint64)), SLOT(udp_bytesWritten(qint64)),
|
||
|
|
Qt::QueuedConnection);
|
||
|
|
|
||
|
|
pool = new StunTransactionPool(StunTransaction::Udp, this);
|
||
|
|
if(debug)
|
||
|
|
pool->setDebugLevel(StunTransactionPool::DL_Packet);
|
||
|
|
else
|
||
|
|
pool->setDebugLevel(StunTransactionPool::DL_Info);
|
||
|
|
connect(pool, SIGNAL(outgoingMessage(const QByteArray &, const QHostAddress &, int)), SLOT(pool_outgoingMessage(const QByteArray &, const QHostAddress &, int)));
|
||
|
|
connect(pool, SIGNAL(needAuthParams()), SLOT(pool_needAuthParams()));
|
||
|
|
connect(pool, SIGNAL(debugLine(const QString &)), SLOT(pool_debugLine(const QString &)));
|
||
|
|
|
||
|
|
pool->setLongTermAuthEnabled(true);
|
||
|
|
if(!relayUser.isEmpty())
|
||
|
|
{
|
||
|
|
pool->setUsername(relayUser);
|
||
|
|
pool->setPassword(relayPass.toUtf8());
|
||
|
|
if(!relayRealm.isEmpty())
|
||
|
|
pool->setRealm(relayRealm);
|
||
|
|
}
|
||
|
|
|
||
|
|
if(!udp->bind())
|
||
|
|
{
|
||
|
|
printf("Error binding to local port.\n");
|
||
|
|
emit quit();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
turn->connectToHost(pool);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if(!relayUser.isEmpty())
|
||
|
|
{
|
||
|
|
turn->setUsername(relayUser);
|
||
|
|
turn->setPassword(relayPass.toUtf8());
|
||
|
|
if(!relayRealm.isEmpty())
|
||
|
|
turn->setRealm(relayRealm);
|
||
|
|
}
|
||
|
|
|
||
|
|
printf("TCP connecting...\n");
|
||
|
|
turn->connectToHost(relayAddr, relayPort, mode == 2 ? TurnClient::TlsMode : TurnClient::PlainMode);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
signals:
|
||
|
|
void quit();
|
||
|
|
|
||
|
|
private:
|
||
|
|
void processDatagram(const QByteArray &buf)
|
||
|
|
{
|
||
|
|
QByteArray data;
|
||
|
|
QHostAddress fromAddr;
|
||
|
|
int fromPort;
|
||
|
|
|
||
|
|
bool notStun;
|
||
|
|
if(!pool->writeIncomingMessage(buf, ¬Stun))
|
||
|
|
{
|
||
|
|
data = turn->processIncomingDatagram(buf, notStun, &fromAddr, &fromPort);
|
||
|
|
if(!data.isNull())
|
||
|
|
processDataPacket(data, fromAddr, fromPort);
|
||
|
|
else
|
||
|
|
printf("Warning: server responded with what doesn't seem to be a STUN or data packet, skipping.\n");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void processDataPacket(const QByteArray &buf, const QHostAddress &addr, int port)
|
||
|
|
{
|
||
|
|
printf("Received %d bytes from %s:%d: [%s]\n", buf.size(), qPrintable(addr.toString()), port, buf.data());
|
||
|
|
|
||
|
|
turn->close();
|
||
|
|
}
|
||
|
|
|
||
|
|
private slots:
|
||
|
|
void do_quit()
|
||
|
|
{
|
||
|
|
ProcessQuit::cleanup();
|
||
|
|
|
||
|
|
turn->close();
|
||
|
|
}
|
||
|
|
|
||
|
|
void udp_readyRead()
|
||
|
|
{
|
||
|
|
while(udp->hasPendingDatagrams())
|
||
|
|
{
|
||
|
|
QByteArray buf(udp->pendingDatagramSize(), 0);
|
||
|
|
QHostAddress from;
|
||
|
|
quint16 fromPort;
|
||
|
|
|
||
|
|
udp->readDatagram(buf.data(), buf.size(), &from, &fromPort);
|
||
|
|
if(from == relayAddr && fromPort == relayPort)
|
||
|
|
{
|
||
|
|
processDatagram(buf);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
printf("Response from unknown sender %s:%d, dropping.\n", qPrintable(from.toString()), fromPort);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void udp_bytesWritten(qint64 bytes)
|
||
|
|
{
|
||
|
|
Q_UNUSED(bytes);
|
||
|
|
bool wasTurnOriginated = writeItems.takeFirst();
|
||
|
|
if(wasTurnOriginated)
|
||
|
|
turn->outgoingDatagramsWritten(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
void pool_outgoingMessage(const QByteArray &packet, const QHostAddress &toAddress, int toPort)
|
||
|
|
{
|
||
|
|
// in this example, we aren't using IP-associated transactions
|
||
|
|
Q_UNUSED(toAddress);
|
||
|
|
Q_UNUSED(toPort);
|
||
|
|
|
||
|
|
writeItems += false;
|
||
|
|
udp->writeDatagram(packet, relayAddr, relayPort);
|
||
|
|
}
|
||
|
|
|
||
|
|
void pool_needAuthParams()
|
||
|
|
{
|
||
|
|
relayUser = prompt("Username:");
|
||
|
|
relayPass = prompt("Password:");
|
||
|
|
|
||
|
|
pool->setUsername(relayUser);
|
||
|
|
pool->setPassword(relayPass.toUtf8());
|
||
|
|
|
||
|
|
QString str = prompt(QString("Realm: [%1]").arg(pool->realm()));
|
||
|
|
if(!str.isEmpty())
|
||
|
|
{
|
||
|
|
relayRealm = str;
|
||
|
|
pool->setRealm(relayRealm);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
relayRealm = pool->realm();
|
||
|
|
|
||
|
|
pool->continueAfterParams();
|
||
|
|
}
|
||
|
|
|
||
|
|
void pool_debugLine(const QString &line)
|
||
|
|
{
|
||
|
|
turn_debugLine(line);
|
||
|
|
}
|
||
|
|
|
||
|
|
void turn_connected()
|
||
|
|
{
|
||
|
|
printf("TCP connected\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
void turn_tlsHandshaken()
|
||
|
|
{
|
||
|
|
printf("TLS handshake completed\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
void turn_closed()
|
||
|
|
{
|
||
|
|
printf("Done\n");
|
||
|
|
emit quit();
|
||
|
|
}
|
||
|
|
|
||
|
|
void turn_needAuthParams()
|
||
|
|
{
|
||
|
|
relayUser = prompt("Username:");
|
||
|
|
relayPass = prompt("Password:");
|
||
|
|
|
||
|
|
turn->setUsername(relayUser);
|
||
|
|
turn->setPassword(relayPass.toUtf8());
|
||
|
|
|
||
|
|
QString str = prompt(QString("Realm: [%1]").arg(turn->realm()));
|
||
|
|
if(!str.isEmpty())
|
||
|
|
{
|
||
|
|
relayRealm = str;
|
||
|
|
turn->setRealm(relayRealm);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
relayRealm = turn->realm();
|
||
|
|
|
||
|
|
turn->continueAfterParams();
|
||
|
|
}
|
||
|
|
|
||
|
|
void turn_retrying()
|
||
|
|
{
|
||
|
|
printf("Mismatch error, retrying...\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
void turn_activated()
|
||
|
|
{
|
||
|
|
StunAllocate *allocate = turn->stunAllocate();
|
||
|
|
|
||
|
|
QHostAddress saddr = allocate->reflexiveAddress();
|
||
|
|
quint16 sport = allocate->reflexivePort();
|
||
|
|
printf("Server says we are %s;%d\n", qPrintable(saddr.toString()), sport);
|
||
|
|
saddr = allocate->relayedAddress();
|
||
|
|
sport = allocate->relayedPort();
|
||
|
|
printf("Server relays via %s;%d\n", qPrintable(saddr.toString()), sport);
|
||
|
|
|
||
|
|
// optional: flag this destination to use a channelbind
|
||
|
|
turn->addChannelPeer(peerAddr, peerPort);
|
||
|
|
|
||
|
|
QByteArray buf = "Hello, world!";
|
||
|
|
printf("Relaying test packet of %d bytes [%s] to %s;%d...\n", buf.size(), buf.data(), qPrintable(peerAddr.toString()), peerPort);
|
||
|
|
turn->write(buf, peerAddr, peerPort);
|
||
|
|
}
|
||
|
|
|
||
|
|
void turn_readyRead()
|
||
|
|
{
|
||
|
|
QHostAddress addr;
|
||
|
|
int port;
|
||
|
|
QByteArray buf = turn->read(&addr, &port);
|
||
|
|
|
||
|
|
processDataPacket(buf, addr, port);
|
||
|
|
}
|
||
|
|
|
||
|
|
void turn_packetsWritten(int count, const QHostAddress &addr, int port)
|
||
|
|
{
|
||
|
|
Q_UNUSED(addr);
|
||
|
|
Q_UNUSED(port);
|
||
|
|
|
||
|
|
printf("%d packet(s) written\n", count);
|
||
|
|
}
|
||
|
|
|
||
|
|
void turn_error(XMPP::TurnClient::Error e)
|
||
|
|
{
|
||
|
|
Q_UNUSED(e);
|
||
|
|
printf("Error: %s\n", qPrintable(turn->errorString()));
|
||
|
|
emit quit();
|
||
|
|
}
|
||
|
|
|
||
|
|
void turn_outgoingDatagram(const QByteArray &buf)
|
||
|
|
{
|
||
|
|
writeItems += true;
|
||
|
|
udp->writeDatagram(buf, relayAddr, relayPort);
|
||
|
|
}
|
||
|
|
|
||
|
|
void turn_debugLine(const QString &line)
|
||
|
|
{
|
||
|
|
printf("%s\n", qPrintable(line));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
void usage()
|
||
|
|
{
|
||
|
|
printf("nettool: simple testing utility\n");
|
||
|
|
printf("usage: nettool (options) [command]\n");
|
||
|
|
printf(" options: --debug, --user=x, --pass=x, --realm=x\n");
|
||
|
|
printf("\n");
|
||
|
|
printf(" netmon monitor network interfaces\n");
|
||
|
|
printf(" rname (-r) [domain] (record type) look up record (default = a)\n");
|
||
|
|
printf(" rnamel [domain] [record type] look up record (long-lived)\n");
|
||
|
|
printf(" raddr [domain] look up AAAA/A\n");
|
||
|
|
printf(" browse [service type] browse for local services\n");
|
||
|
|
printf(" rservi [instance] [service type] look up browsed instance\n");
|
||
|
|
printf(" rservd [domain] [service type] look up normal SRV\n");
|
||
|
|
printf(" rservp [domain] [port] look up non-SRV\n");
|
||
|
|
printf(" pserv [inst] [type] [port] (attr) (-a [rec]) publish service instance\n");
|
||
|
|
printf(" stun [addr](;port) (local port) STUN binding\n");
|
||
|
|
printf(" turn [mode] [relayaddr](;port) [peeraddr](;port) TURN UDP echo test\n");
|
||
|
|
printf("\n");
|
||
|
|
printf("record types: a aaaa ptr srv mx txt hinfo null\n");
|
||
|
|
printf("service types: _service._proto format (e.g. \"_xmpp-client._tcp\")\n");
|
||
|
|
printf("attributes: var0[=val0],...,varn[=valn]\n");
|
||
|
|
printf("rname -r: for null type, dump raw record data to stdout\n");
|
||
|
|
printf("pserv -a: add extra record. format: null:filename.dat\n");
|
||
|
|
printf("turn modes: udp tcp tcp-tls\n");
|
||
|
|
printf("\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(int argc, char **argv)
|
||
|
|
{
|
||
|
|
QCA::Initializer qcaInit;
|
||
|
|
QCoreApplication qapp(argc, argv);
|
||
|
|
|
||
|
|
QStringList args = qapp.arguments();
|
||
|
|
args.removeFirst();
|
||
|
|
|
||
|
|
QString user, pass, realm;
|
||
|
|
bool debug = false;
|
||
|
|
|
||
|
|
for(int n = 0; n < args.count(); ++n)
|
||
|
|
{
|
||
|
|
QString s = args[n];
|
||
|
|
if(!s.startsWith("--"))
|
||
|
|
continue;
|
||
|
|
QString var;
|
||
|
|
QString val;
|
||
|
|
int x = s.indexOf('=');
|
||
|
|
if(x != -1)
|
||
|
|
{
|
||
|
|
var = s.mid(2, x - 2);
|
||
|
|
val = s.mid(x + 1);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
var = s.mid(2);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool known = true;
|
||
|
|
|
||
|
|
if(var == "debug")
|
||
|
|
debug = true;
|
||
|
|
else if(var == "user")
|
||
|
|
user = val;
|
||
|
|
else if(var == "pass")
|
||
|
|
pass = val;
|
||
|
|
else if(var == "realm")
|
||
|
|
realm = val;
|
||
|
|
else
|
||
|
|
known = false;
|
||
|
|
|
||
|
|
if(!known)
|
||
|
|
{
|
||
|
|
fprintf(stderr, "Unknown option '%s'.\n", qPrintable(var));
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
args.removeAt(n);
|
||
|
|
--n; // adjust position
|
||
|
|
}
|
||
|
|
|
||
|
|
if(args.isEmpty())
|
||
|
|
{
|
||
|
|
usage();
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(args[0] == "netmon")
|
||
|
|
{
|
||
|
|
NetMonitor a;
|
||
|
|
QObject::connect(&a, SIGNAL(quit()), &qapp, SLOT(quit()));
|
||
|
|
QTimer::singleShot(0, &a, SLOT(start()));
|
||
|
|
qapp.exec();
|
||
|
|
}
|
||
|
|
else if(args[0] == "rname" || args[0] == "rnamel")
|
||
|
|
{
|
||
|
|
bool null_dump = false;
|
||
|
|
for(int n = 1; n < args.count(); ++n)
|
||
|
|
{
|
||
|
|
if(args[n] == "-r")
|
||
|
|
{
|
||
|
|
null_dump = true;
|
||
|
|
args.removeAt(n);
|
||
|
|
--n;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if(args.count() < 2)
|
||
|
|
{
|
||
|
|
usage();
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
if(args[0] == "rnamel" && args.count() < 3)
|
||
|
|
{
|
||
|
|
usage();
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
int x = NameRecord::A;
|
||
|
|
if(args.count() >= 3)
|
||
|
|
{
|
||
|
|
x = str2rtype(args[2]);
|
||
|
|
if(x == -1)
|
||
|
|
{
|
||
|
|
usage();
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ResolveName a;
|
||
|
|
a.name = args[1];
|
||
|
|
a.type = (NameRecord::Type)x;
|
||
|
|
a.longlived = (args[0] == "rnamel") ? true : false;
|
||
|
|
if(args[0] == "rname" && null_dump)
|
||
|
|
a.null_dump = true;
|
||
|
|
QObject::connect(&a, SIGNAL(quit()), &qapp, SLOT(quit()));
|
||
|
|
QTimer::singleShot(0, &a, SLOT(start()));
|
||
|
|
qapp.exec();
|
||
|
|
}
|
||
|
|
else if(args[0] == "raddr")
|
||
|
|
{
|
||
|
|
if(args.count() < 2)
|
||
|
|
{
|
||
|
|
usage();
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
ResolveAddr a;
|
||
|
|
a.name = args[1];
|
||
|
|
QObject::connect(&a, SIGNAL(quit()), &qapp, SLOT(quit()));
|
||
|
|
QTimer::singleShot(0, &a, SLOT(start()));
|
||
|
|
qapp.exec();
|
||
|
|
}
|
||
|
|
else if(args[0] == "browse")
|
||
|
|
{
|
||
|
|
if(args.count() < 2)
|
||
|
|
{
|
||
|
|
usage();
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
BrowseServices a;
|
||
|
|
a.type = args[1];
|
||
|
|
QObject::connect(&a, SIGNAL(quit()), &qapp, SLOT(quit()));
|
||
|
|
QTimer::singleShot(0, &a, SLOT(start()));
|
||
|
|
qapp.exec();
|
||
|
|
}
|
||
|
|
else if(args[0] == "rservi" || args[0] == "rservd" || args[0] == "rservp")
|
||
|
|
{
|
||
|
|
// they all take 2 params
|
||
|
|
if(args.count() < 3)
|
||
|
|
{
|
||
|
|
usage();
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
ResolveService a;
|
||
|
|
if(args[0] == "rservi")
|
||
|
|
{
|
||
|
|
a.mode = 0;
|
||
|
|
a.instance = args[1];
|
||
|
|
a.type = args[2];
|
||
|
|
}
|
||
|
|
else if(args[0] == "rservd")
|
||
|
|
{
|
||
|
|
a.mode = 1;
|
||
|
|
a.domain = args[1];
|
||
|
|
a.type = args[2];
|
||
|
|
}
|
||
|
|
else // rservp
|
||
|
|
{
|
||
|
|
a.mode = 2;
|
||
|
|
a.domain = args[1];
|
||
|
|
a.port = args[2].toInt();
|
||
|
|
}
|
||
|
|
QObject::connect(&a, SIGNAL(quit()), &qapp, SLOT(quit()));
|
||
|
|
QTimer::singleShot(0, &a, SLOT(start()));
|
||
|
|
qapp.exec();
|
||
|
|
}
|
||
|
|
else if(args[0] == "pserv")
|
||
|
|
{
|
||
|
|
QStringList addrecs;
|
||
|
|
for(int n = 1; n < args.count(); ++n)
|
||
|
|
{
|
||
|
|
if(args[n] == "-a")
|
||
|
|
{
|
||
|
|
if(n + 1 < args.count())
|
||
|
|
{
|
||
|
|
addrecs += args[n + 1];
|
||
|
|
args.removeAt(n);
|
||
|
|
args.removeAt(n);
|
||
|
|
--n;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
usage();
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
QByteArray extra_null;
|
||
|
|
for(int n = 0; n < addrecs.count(); ++n)
|
||
|
|
{
|
||
|
|
const QString &str = addrecs[n];
|
||
|
|
int x = str.indexOf(':');
|
||
|
|
if(x == -1 || str.mid(0, x) != "null")
|
||
|
|
{
|
||
|
|
usage();
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
QString null_file = str.mid(x + 1);
|
||
|
|
|
||
|
|
if(!null_file.isEmpty())
|
||
|
|
{
|
||
|
|
QFile f(null_file);
|
||
|
|
if(!f.open(QFile::ReadOnly))
|
||
|
|
{
|
||
|
|
printf("can't read file\n");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
extra_null = f.readAll();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if(args.count() < 4)
|
||
|
|
{
|
||
|
|
usage();
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
QMap<QString,QByteArray> attribs;
|
||
|
|
if(args.count() > 4)
|
||
|
|
{
|
||
|
|
QStringList parts = args[4].split(',');
|
||
|
|
for(int n = 0; n < parts.count(); ++n)
|
||
|
|
{
|
||
|
|
const QString &str = parts[n];
|
||
|
|
int x = str.indexOf('=');
|
||
|
|
if(x != -1)
|
||
|
|
attribs.insert(str.mid(0, x), str.mid(x + 1).toUtf8());
|
||
|
|
else
|
||
|
|
attribs.insert(str, QByteArray());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
PublishService a;
|
||
|
|
a.instance = args[1];
|
||
|
|
a.type = args[2];
|
||
|
|
a.port = args[3].toInt();
|
||
|
|
a.attribs = attribs;
|
||
|
|
a.extra_null = extra_null;
|
||
|
|
QObject::connect(&a, SIGNAL(quit()), &qapp, SLOT(quit()));
|
||
|
|
QTimer::singleShot(0, &a, SLOT(start()));
|
||
|
|
qapp.exec();
|
||
|
|
}
|
||
|
|
else if(args[0] == "stun")
|
||
|
|
{
|
||
|
|
if(args.count() < 2)
|
||
|
|
{
|
||
|
|
usage();
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
QString addrstr, portstr;
|
||
|
|
int x = args[1].indexOf(';');
|
||
|
|
if(x != -1)
|
||
|
|
{
|
||
|
|
addrstr = args[1].mid(0, x);
|
||
|
|
portstr = args[1].mid(x + 1);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
addrstr = args[1];
|
||
|
|
|
||
|
|
QHostAddress addr = QHostAddress(addrstr);
|
||
|
|
if(addr.isNull())
|
||
|
|
{
|
||
|
|
printf("Error: addr must be an IP address\n");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
int port = 3478;
|
||
|
|
if(!portstr.isEmpty())
|
||
|
|
port = portstr.toInt();
|
||
|
|
|
||
|
|
int localPort = -1;
|
||
|
|
if(args.count() >= 3)
|
||
|
|
localPort = args[2].toInt();
|
||
|
|
|
||
|
|
if(!QCA::isSupported("hmac(sha1)"))
|
||
|
|
{
|
||
|
|
printf("Error: Need hmac(sha1) support to use STUN.\n");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
StunBind a;
|
||
|
|
a.debug = debug;
|
||
|
|
a.localPort = localPort;
|
||
|
|
a.addr = addr;
|
||
|
|
a.port = port;
|
||
|
|
QObject::connect(&a, SIGNAL(quit()), &qapp, SLOT(quit()));
|
||
|
|
QTimer::singleShot(0, &a, SLOT(start()));
|
||
|
|
qapp.exec();
|
||
|
|
}
|
||
|
|
else if(args[0] == "turn")
|
||
|
|
{
|
||
|
|
if(args.count() < 4)
|
||
|
|
{
|
||
|
|
usage();
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
int mode;
|
||
|
|
if(args[1] == "udp")
|
||
|
|
mode = 0;
|
||
|
|
else if(args[1] == "tcp")
|
||
|
|
mode = 1;
|
||
|
|
else if(args[1] == "tcp-tls")
|
||
|
|
mode = 2;
|
||
|
|
else
|
||
|
|
{
|
||
|
|
usage();
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
QString addrstr, portstr;
|
||
|
|
int x = args[2].indexOf(';');
|
||
|
|
if(x != -1)
|
||
|
|
{
|
||
|
|
addrstr = args[2].mid(0, x);
|
||
|
|
portstr = args[2].mid(x + 1);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
addrstr = args[2];
|
||
|
|
|
||
|
|
QHostAddress raddr = QHostAddress(addrstr);
|
||
|
|
if(raddr.isNull())
|
||
|
|
{
|
||
|
|
printf("Error: relayaddr must be an IP address\n");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
int rport = 3478;
|
||
|
|
if(!portstr.isEmpty())
|
||
|
|
rport = portstr.toInt();
|
||
|
|
|
||
|
|
portstr.clear();
|
||
|
|
x = args[3].indexOf(';');
|
||
|
|
if(x != -1)
|
||
|
|
{
|
||
|
|
addrstr = args[3].mid(0, x);
|
||
|
|
portstr = args[3].mid(x + 1);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
addrstr = args[3];
|
||
|
|
|
||
|
|
QHostAddress paddr = QHostAddress(addrstr);
|
||
|
|
if(raddr.isNull())
|
||
|
|
{
|
||
|
|
printf("Error: peeraddr must be an IP address\n");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
int pport = 4588;
|
||
|
|
if(!portstr.isEmpty())
|
||
|
|
pport = portstr.toInt();
|
||
|
|
|
||
|
|
if(!QCA::isSupported("hmac(sha1)"))
|
||
|
|
{
|
||
|
|
printf("Error: Need hmac(sha1) support to use TURN.\n");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(mode == 2 && !QCA::isSupported("tls"))
|
||
|
|
{
|
||
|
|
printf("Error: Need tls support to use tcp-tls mode.\n");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
TurnClientTest a;
|
||
|
|
a.mode = mode;
|
||
|
|
a.debug = debug;
|
||
|
|
a.relayAddr = raddr;
|
||
|
|
a.relayPort = rport;
|
||
|
|
a.relayUser = user;
|
||
|
|
a.relayPass = pass;
|
||
|
|
a.relayRealm = realm;
|
||
|
|
a.peerAddr = paddr;
|
||
|
|
a.peerPort = pport;
|
||
|
|
QObject::connect(&a, SIGNAL(quit()), &qapp, SLOT(quit()));
|
||
|
|
QTimer::singleShot(0, &a, SLOT(start()));
|
||
|
|
qapp.exec();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
usage();
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
#include "main.moc"
|