add a DTLSSession object to handle GnuTLS DTLS sessions

This commit is contained in:
Stephen Birarda 2014-04-01 13:08:00 -07:00
parent 62da4d622d
commit caf2473df8
9 changed files with 206 additions and 98 deletions

View file

@ -10,6 +10,8 @@
#include <QtCore/QThread>
#include <QtCore/QTimer>
#include <gnutls/gnutls.h>
#include <AccountManager.h>
#include <Assignment.h>
#include <Logging.h>
@ -31,6 +33,8 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
QCoreApplication(argc, argv),
_currentAssignment()
{
gnutls_global_init();
setOrganizationName("High Fidelity");
setOrganizationDomain("highfidelity.io");
setApplicationName("assignment-client");

View file

@ -39,7 +39,9 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_x509Credentials(NULL),
_dhParams(NULL),
_priorityCache(NULL)
{
{
gnutls_global_init();
setOrganizationName("High Fidelity");
setOrganizationDomain("highfidelity.io");
setApplicationName("domain-server");
@ -80,28 +82,30 @@ DomainServer::DomainServer(int argc, char* argv[]) :
bool DomainServer::optionallySetupDTLS() {
if (readX509KeyAndCertificate()) {
qDebug() << "Generating Diffie-Hellman parameters.";
// generate Diffie-Hellman parameters
// When short bit length is used, it might be wise to regenerate parameters often.
int dhBits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY);
_dhParams = new gnutls_dh_params_t;
gnutls_dh_params_init(_dhParams);
gnutls_dh_params_generate2(*_dhParams, dhBits);
qDebug() << "Successfully generated Diffie-Hellman parameters.";
// set the D-H paramters on the X509 credentials
gnutls_certificate_set_dh_params(*_x509Credentials, *_dhParams);
_priorityCache = new gnutls_priority_t;
const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.2:%SERVER_PRECEDENCE";
gnutls_priority_init(_priorityCache, DTLS_PRIORITY_STRING, NULL);
_isUsingDTLS = true;
qDebug() << "Initial DTLS setup complete.";
if (_x509Credentials) {
qDebug() << "Generating Diffie-Hellman parameters.";
// generate Diffie-Hellman parameters
// When short bit length is used, it might be wise to regenerate parameters often.
int dhBits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY);
_dhParams = new gnutls_dh_params_t;
gnutls_dh_params_init(_dhParams);
gnutls_dh_params_generate2(*_dhParams, dhBits);
qDebug() << "Successfully generated Diffie-Hellman parameters.";
// set the D-H paramters on the X509 credentials
gnutls_certificate_set_dh_params(*_x509Credentials, *_dhParams);
_priorityCache = new gnutls_priority_t;
const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.2:%SERVER_PRECEDENCE";
gnutls_priority_init(_priorityCache, DTLS_PRIORITY_STRING, NULL);
_isUsingDTLS = true;
qDebug() << "Initial DTLS setup complete.";
}
return true;
} else {
@ -180,7 +184,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
domainServerPort = _argumentList.value(argumentIndex + 1).toUShort();
}
unsigned short domainServerDTLSPort = -1;
unsigned short domainServerDTLSPort = 0;
if (_isUsingDTLS) {
domainServerDTLSPort = DEFAULT_DOMAIN_SERVER_DTLS_PORT;
@ -532,69 +536,17 @@ void DomainServer::readAvailableDatagrams() {
HifiSockAddr senderSockAddr;
QByteArray receivedPacket;
static QByteArray assignmentPacket = byteArrayWithPopulatedHeader(PacketTypeCreateAssignment);
static int numAssignmentPacketHeaderBytes = assignmentPacket.size();
while (nodeList->getNodeSocket().hasPendingDatagrams()) {
receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(),
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
if (!_isUsingDTLS) {
// not using DTLS, process datagram normally
processDatagram(receivedPacket, senderSockAddr);
} else {
// we're using DTLS, so tell the sender to get back to us using DTLS
static QByteArray dtlsRequiredPacket = byteArrayWithPopulatedHeader(PacketTypeDomainServerRequireDTLS);
static int numBytesDTLSHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeDomainServerRequireDTLS);
if (dtlsRequiredPacket.size() == numBytesDTLSHeader) {
// pack the port that we accept DTLS traffic on
unsigned short dtlsPort = nodeList->getDTLSSocket().localPort();
dtlsRequiredPacket.replace(numBytesDTLSHeader, sizeof(dtlsPort), reinterpret_cast<const char*>(&dtlsPort));
}
if (packetTypeForPacket(receivedPacket) && nodeList->packetVersionAndHashMatch(receivedPacket)) {
nodeList->writeUnverifiedDatagram(dtlsRequiredPacket, senderSockAddr);
}
}
}
void DomainServer::readAvailableDTLSDatagrams() {
}
void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) {
NodeList* nodeList = NodeList::getInstance();
static QByteArray assignmentPacket = byteArrayWithPopulatedHeader(PacketTypeCreateAssignment);
static int numAssignmentPacketHeaderBytes = assignmentPacket.size();
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
PacketType requestType = packetTypeForPacket(receivedPacket);
if (requestType == PacketTypeDomainListRequest) {
QUuid nodeUUID = uuidFromPacketHeader(receivedPacket);
if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) {
NodeType_t throwawayNodeType;
HifiSockAddr nodePublicAddress, nodeLocalAddress;
int numNodeInfoBytes = parseNodeDataFromByteArray(throwawayNodeType, nodePublicAddress, nodeLocalAddress,
receivedPacket, senderSockAddr);
SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID, nodePublicAddress, nodeLocalAddress);
// update last receive to now
quint64 timeNow = usecTimestampNow();
checkInNode->setLastHeardMicrostamp(timeNow);
sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestListFromPacket(receivedPacket, numNodeInfoBytes));
} else {
// new node - add this node to our NodeList
// and send back session UUID right away
addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr);
}
} else if (requestType == PacketTypeRequestAssignment) {
// construct the requested assignment from the packet data
Assignment requestAssignment(receivedPacket);
@ -626,7 +578,7 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
} else {
if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) {
qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType()
<< "from" << senderSockAddr;
<< "from" << senderSockAddr;
noisyMessage = true;
}
}
@ -634,6 +586,58 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
if (noisyMessage) {
lastNoisyMessage = timeNow;
}
} else if (!_isUsingDTLS) {
// not using DTLS, process datagram normally
processDatagram(receivedPacket, senderSockAddr);
} else {
// we're using DTLS, so tell the sender to get back to us using DTLS
static QByteArray dtlsRequiredPacket = byteArrayWithPopulatedHeader(PacketTypeDomainServerRequireDTLS);
static int numBytesDTLSHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeDomainServerRequireDTLS);
if (dtlsRequiredPacket.size() == numBytesDTLSHeader) {
// pack the port that we accept DTLS traffic on
unsigned short dtlsPort = nodeList->getDTLSSocket().localPort();
dtlsRequiredPacket.replace(numBytesDTLSHeader, sizeof(dtlsPort), reinterpret_cast<const char*>(&dtlsPort));
}
nodeList->writeUnverifiedDatagram(dtlsRequiredPacket, senderSockAddr);
}
}
}
void DomainServer::readAvailableDTLSDatagrams() {
}
void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) {
NodeList* nodeList = NodeList::getInstance();
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
PacketType requestType = packetTypeForPacket(receivedPacket);
if (requestType == PacketTypeDomainListRequest) {
QUuid nodeUUID = uuidFromPacketHeader(receivedPacket);
if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) {
NodeType_t throwawayNodeType;
HifiSockAddr nodePublicAddress, nodeLocalAddress;
int numNodeInfoBytes = parseNodeDataFromByteArray(throwawayNodeType, nodePublicAddress, nodeLocalAddress,
receivedPacket, senderSockAddr);
SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID, nodePublicAddress, nodeLocalAddress);
// update last receive to now
quint64 timeNow = usecTimestampNow();
checkInNode->setLastHeardMicrostamp(timeNow);
sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestListFromPacket(receivedPacket, numNodeInfoBytes));
} else {
// new node - add this node to our NodeList
// and send back session UUID right away
addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr);
}
} else if (requestType == PacketTypeNodeJsonStats) {
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
if (matchingNode) {

View file

@ -24,8 +24,6 @@ int main(int argc, char* argv[]) {
setvbuf(stdout, NULL, _IOLBF, 0);
#endif
gnutls_global_init();
qInstallMessageHandler(Logging::verboseMessageHandler);
DomainServer domainServer(argc, argv);

View file

@ -0,0 +1,43 @@
//
// DTLSSession.cpp
// hifi
//
// Created by Stephen Birarda on 2014-04-01.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#include "DTLSSession.h"
static int socketPullTimeout(gnutls_transport_ptr_t ptr, unsigned int ms) {
return 1;
}
static ssize_t socketPull(gnutls_transport_ptr_t ptr, void* buffer, size_t size) {
DTLSSession* session = static_cast<DTLSSession*>(ptr);
QUdpSocket& dtlsSocket = session->_dtlsSocket;
if (dtlsSocket.hasPendingDatagrams()) {
return dtlsSocket.read(reinterpret_cast<char*>(buffer), size);
} else {
gnutls_transport_set_errno(session->_gnutlsSession, GNUTLS_E_AGAIN);
return 0;
}
}
static ssize_t socketPush(gnutls_transport_ptr_t ptr, const void* buffer, size_t size) {
DTLSSession* session = static_cast<DTLSSession*>(ptr);
QUdpSocket& dtlsSocket = session->_dtlsSocket;
if (dtlsSocket.state() != QAbstractSocket::ConnectedState) {
gnutls_transport_set_errno(session->_gnutlsSession, GNUTLS_E_AGAIN);
return -1;
}
return dtlsSocket.write(reinterpret_cast<const char*>(buffer), size);
}
DTLSSession::DTLSSession(QUdpSocket& dtlsSocket) :
_dtlsSocket(dtlsSocket)
{
}

View file

@ -0,0 +1,32 @@
//
// DTLSSession.h
// hifi
//
// Created by Stephen Birarda on 2014-04-01.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __hifi__DTLSSession__
#define __hifi__DTLSSession__
#include <QtNetwork/QUdpSocket>
#include <gnutls/gnutls.h>
static int socketPullTimeout(gnutls_transport_ptr_t ptr, unsigned int ms);
static ssize_t socketPull(gnutls_transport_ptr_t ptr, void* buffer, size_t size);
static ssize_t socketPush(gnutls_transport_ptr_t ptr, const void* buffer, size_t size);
class DTLSSession {
public:
DTLSSession(QUdpSocket& dtlsSocket);
friend int socketPullTimeout(gnutls_transport_ptr_t ptr, unsigned int ms);
friend ssize_t socketPull(gnutls_transport_ptr_t ptr, void* buffer, size_t size);
friend ssize_t socketPush(gnutls_transport_ptr_t ptr, const void* buffer, size_t size);
private:
QUdpSocket& _dtlsSocket;
gnutls_session_t _gnutlsSession;
};
#endif /* defined(__hifi__DTLSSession__) */

View file

@ -6,8 +6,9 @@
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QJsonObject>
#include <gnutls/dtls.h>
#include "NodeList.h"
#include "PacketHeaders.h"
#include "DomainInfo.h"
@ -16,12 +17,17 @@ DomainInfo::DomainInfo() :
_uuid(),
_sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
_assignmentUUID(),
_isConnected(false),
_requiresDTLS(false),
_isConnected(false)
_dtlsSession(NULL)
{
}
DomainInfo::~DomainInfo() {
delete _dtlsSession;
}
void DomainInfo::clearConnectionInfo() {
_uuid = QUuid();
_isConnected = false;
@ -34,6 +40,12 @@ void DomainInfo::reset() {
_requiresDTLS = false;
}
void DomainInfo::initializeDTLSSession() {
if (!_dtlsSession) {
_dtlsSession = new DTLSSession(NodeList::getInstance()->getDTLSSocket());
}
}
void DomainInfo::setSockAddr(const HifiSockAddr& sockAddr) {
if (_sockAddr != sockAddr) {
// we should reset on a sockAddr change
@ -107,8 +119,11 @@ void DomainInfo::parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPac
unsigned short dtlsPort = 0;
memcpy(&dtlsPort, dtlsRequirementPacket.data() + numBytesPacketHeader, sizeof(dtlsPort));
qDebug() << "domain-server DTLS port changed to" << dtlsPort << "- Enabling DTLS.";
_sockAddr.setPort(dtlsPort);
_requiresDTLS = true;
qDebug() << "domain-server DTLS port changed to" << dtlsPort << "- DTLS enabled.";
initializeDTLSSession();
}

View file

@ -14,6 +14,7 @@
#include <QtCore/QUrl>
#include <QtNetwork/QHostInfo>
#include "DTLSSession.h"
#include "HifiSockAddr.h"
const QString DEFAULT_DOMAIN_HOSTNAME = "alpha.highfidelity.io";
@ -24,6 +25,7 @@ class DomainInfo : public QObject {
Q_OBJECT
public:
DomainInfo();
~DomainInfo();
void clearConnectionInfo();
@ -54,15 +56,18 @@ private slots:
signals:
void hostnameChanged(const QString& hostname);
void connectedToDomain(const QString& hostname);
void initializedDTLSSession();
private:
void reset();
void initializeDTLSSession();
QUuid _uuid;
QString _hostname;
HifiSockAddr _sockAddr;
QUuid _assignmentUUID;
bool _requiresDTLS;
bool _isConnected;
bool _requiresDTLS;
DTLSSession* _dtlsSession;
};
#endif /* defined(__hifi__DomainInfo__) */

View file

@ -62,6 +62,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
_nodeHash(),
_nodeHashMutex(QMutex::Recursive),
_nodeSocket(this),
_dtlsSocket(NULL),
_ownerType(newOwnerType),
_nodeTypesOfInterest(),
_sessionUUID(),
@ -78,9 +79,11 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
qDebug() << "NodeList socket is listening on" << _nodeSocket.localPort();
if (dtlsListenPort > 0) {
// we have a specfic DTLS port, bind that socket now
_dtlsSocket.bind(QHostAddress::AnyIPv4, dtlsListenPort);
qDebug() << "NodeList DTLS socket is listening on" << _dtlsSocket.localPort();
// only create the DTLS socket during constructor if a custom port is passed
_dtlsSocket = new QUdpSocket(this);
_dtlsSocket->bind(QHostAddress::AnyIPv4, dtlsListenPort);
qDebug() << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort();
}
// clear our NodeList when the domain changes
@ -96,12 +99,15 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
}
QUdpSocket& NodeList::getDTLSSocket() {
if (_dtlsSocket.state() == QAbstractSocket::UnconnectedState) {
_dtlsSocket.bind(QHostAddress::AnyIPv4);
qDebug() << "NodeList DTLS socket is listening on" << _dtlsSocket.localPort();
if (!_dtlsSocket) {
// DTLS socket getter called but no DTLS socket exists, create it now
_dtlsSocket = new QUdpSocket(this);
_dtlsSocket->bind(QHostAddress::AnyIPv4, 0, QAbstractSocket::DontShareAddress);
qDebug() << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort();
}
return _dtlsSocket;
return *_dtlsSocket;
}
void NodeList::changeSendSocketBufferSize(int numSendBytes) {
@ -569,7 +575,7 @@ void NodeList::sendDomainServerCheckIn() {
// we don't know our public socket and we need to send it to the domain server
// send a STUN request to figure it out
sendSTUNRequest();
} else if (!_domainInfo.getIP().isNull()) {
} else if (!_domainInfo.getIP().isNull()) {
// construct the DS check in packet
PacketType domainPacketType = _sessionUUID.isNull() ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest;
@ -580,7 +586,6 @@ void NodeList::sendDomainServerCheckIn() {
QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID);
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
// pack our data to send to the domain-server
packetStream << _ownerType << _publicSockAddr
<< HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort())

View file

@ -29,6 +29,8 @@
#include <QtNetwork/QHostAddress>
#include <QtNetwork/QUdpSocket>
#include <gnutls/gnutls.h>
#include "DomainInfo.h"
#include "Node.h"
@ -165,7 +167,7 @@ private:
NodeHash _nodeHash;
QMutex _nodeHashMutex;
QUdpSocket _nodeSocket;
QUdpSocket _dtlsSocket;
QUdpSocket* _dtlsSocket;
NodeType_t _ownerType;
NodeSet _nodeTypesOfInterest;
DomainInfo _domainInfo;