initial handshaking for DTLS sessions between DS and clients

This commit is contained in:
Stephen Birarda 2014-04-02 17:29:14 -07:00
parent dc3a4d4957
commit e1cae6d295
19 changed files with 200 additions and 72 deletions

View file

@ -797,7 +797,7 @@ AnimationServer::AnimationServer(int &argc, char **argv) :
QTimer* domainServerTimer = new QTimer(this);
connect(domainServerTimer, SIGNAL(timeout()), nodeList, SLOT(sendDomainServerCheckIn()));
domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000);
domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
QTimer* silentNodeTimer = new QTimer(this);
connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));

View file

@ -369,7 +369,7 @@ void AudioMixer::sendStatsPacket() {
statsObject["average_mixes_per_listener"] = 0.0;
}
ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
// ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
_sumListeners = 0;
_sumMixes = 0;

View file

@ -9,6 +9,8 @@
#ifndef __hifi__DTLSServerSession__
#define __hifi__DTLSServerSession__
#include <gnutls/dtls.h>
#include <DTLSSession.h>
class DTLSServerSession : public DTLSSession {

View file

@ -96,8 +96,12 @@ bool DomainServer::optionallySetupDTLS() {
// set the D-H paramters on the X509 credentials
gnutls_certificate_set_dh_params(*_x509Credentials, *_dhParams);
// setup the key used for cookie verification
_cookieKey = new gnutls_datum_t;
gnutls_key_generate(_cookieKey, GNUTLS_COOKIE_KEY_SIZE);
_priorityCache = new gnutls_priority_t;
const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.2:%SERVER_PRECEDENCE";
const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.0:%SERVER_PRECEDENCE";
gnutls_priority_init(_priorityCache, DTLS_PRIORITY_STRING, NULL);
_isUsingDTLS = true;
@ -612,20 +616,55 @@ void DomainServer::readAvailableDTLSDatagrams() {
static socklen_t sockAddrSize = sizeof(senderSockAddr);
while (dtlsSocket.hasPendingDatagrams()) {
qDebug() << "Looking at a datagram";
// check if we have an active DTLS session for this sender
QByteArray peekDatagram(dtlsSocket.pendingDatagramSize(), 0);
recvfrom(dtlsSocket.socketDescriptor(), peekDatagram.data(), dtlsSocket.pendingDatagramSize(),
MSG_PEEK, &senderSockAddr, &sockAddrSize);
DTLSSession* existingSession = _dtlsSessions.value(HifiSockAddr(&senderSockAddr));
qDebug() << "Checking for a session with" << HifiSockAddr(&senderSockAddr);
HifiSockAddr senderHifiSockAddr(&senderSockAddr);
DTLSServerSession* existingSession = _dtlsSessions.value(senderHifiSockAddr);
if (existingSession) {
// use GnuTLS to receive the encrypted data
// check if we have completed handshake with this user
int handshakeReturn = gnutls_handshake(*existingSession->getGnuTLSSession());
qDebug() << "Handshake return for user is" << handshakeReturn;
} else {
// no existing session - set up a new session now
// first we verify the cookie
// see http://gnutls.org/manual/html_node/DTLS-sessions.html for why this is required
gnutls_dtls_prestate_st prestate;
memset(&prestate, 0, sizeof(prestate));
int cookieValid = gnutls_dtls_cookie_verify(_cookieKey, &senderSockAddr, sizeof(senderSockAddr),
peekDatagram.data(), peekDatagram.size(), &prestate);
if (cookieValid < 0) {
// the cookie sent by the client was not valid
// send a valid one
DTLSServerSession tempServerSession(LimitedNodeList::getInstance()->getDTLSSocket(), senderHifiSockAddr);
gnutls_dtls_cookie_send(_cookieKey, &senderSockAddr, sizeof(senderSockAddr), &prestate,
&tempServerSession, DTLSSession::socketPush);
// acutally pull the peeked data off the network stack so that it gets discarded
dtlsSocket.readDatagram(peekDatagram.data(), peekDatagram.size());
} else {
// cookie valid but no existing session - set up a new session now
DTLSServerSession* newServerSession = new DTLSServerSession(LimitedNodeList::getInstance()->getDTLSSocket(),
senderHifiSockAddr);
gnutls_session_t* gnutlsSession = newServerSession->getGnuTLSSession();
gnutls_priority_set(*gnutlsSession, *_priorityCache);
gnutls_credentials_set(*gnutlsSession, GNUTLS_CRD_CERTIFICATE, *_x509Credentials);
gnutls_dtls_prestate_set(*gnutlsSession, &prestate);
// handshake to begin the session
int handshakeReturn = gnutls_handshake(*gnutlsSession);
qDebug() << "initial handshake return" << handshakeReturn;
qDebug() << "Beginning DTLS session with node at" << senderHifiSockAddr;
_dtlsSessions[senderHifiSockAddr] = newServerSession;
}
}
}
}

View file

@ -89,6 +89,7 @@ private:
bool _isUsingDTLS;
gnutls_certificate_credentials_t* _x509Credentials;
gnutls_dh_params_t* _dhParams;
gnutls_datum_t* _cookieKey;
gnutls_priority_t* _priorityCache;
QHash<HifiSockAddr, DTLSServerSession*> _dtlsSessions;

View file

@ -18,6 +18,7 @@
#include <QtNetwork/QNetworkReply>
#include <qendian.h>
#include <LimitedNodeList.h>
#include <SharedUtil.h>
#include "AudioRingBuffer.h"

View file

@ -10,7 +10,7 @@
#include <QtDebug>
#include <SharedUtil.h>
#include <LimitedNodeList.h>
#include "DatagramSequencer.h"
#include "MetavoxelMessages.h"

View file

@ -8,8 +8,28 @@
#include "DTLSClientSession.h"
gnutls_certificate_credentials_t* DTLSClientSession::x509CACredentials() {
static gnutls_certificate_credentials_t x509Credentials;
static bool credentialsInitialized = false;
if (!credentialsInitialized) {
gnutls_certificate_allocate_credentials(&x509Credentials);
qDebug() << "processed" << gnutls_certificate_set_x509_trust_file(x509Credentials, "/Users/birarda/code/hifi/certs/root-ca.crt", GNUTLS_X509_FMT_PEM);
}
return &x509Credentials;
}
int verifyX509Certificate(gnutls_session_t session) {
int certificateType = gnutls_certificate_type_get(session);
qDebug() << "Verifying a certificate of type" << certificateType;
return 0;
}
DTLSClientSession::DTLSClientSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket) :
DTLSSession(GNUTLS_CLIENT, dtlsSocket, destinationSocket)
{
gnutls_credentials_set(_gnutlsSession, GNUTLS_CRD_CERTIFICATE, DTLSSession::x509CACredentials());
gnutls_priority_set_direct(_gnutlsSession, "PERFORMANCE", NULL);
gnutls_credentials_set(_gnutlsSession, GNUTLS_CRD_CERTIFICATE, x509CACredentials());
}

View file

@ -14,6 +14,8 @@
class DTLSClientSession : public DTLSSession {
public:
DTLSClientSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket);
static gnutls_certificate_credentials_t* x509CACredentials();
};
#endif /* defined(__hifi__DTLSClientSession__) */

View file

@ -8,46 +8,88 @@
#include <gnutls/dtls.h>
#include "NodeList.h"
#include "DTLSSession.h"
int DTLSSession::socketPullTimeout(gnutls_transport_ptr_t ptr, unsigned int ms) {
return 1;
DTLSSession* session = static_cast<DTLSSession*>(ptr);
QUdpSocket& dtlsSocket = session->_dtlsSocket;
if (dtlsSocket.hasPendingDatagrams()) {
// peek the data on stack to see if it belongs to this session
static sockaddr senderSockAddr;
static socklen_t sockAddrSize = sizeof(senderSockAddr);
QByteArray peekDatagram(dtlsSocket.pendingDatagramSize(), 0);
recvfrom(dtlsSocket.socketDescriptor(), peekDatagram.data(), dtlsSocket.pendingDatagramSize(),
MSG_PEEK, &senderSockAddr, &sockAddrSize);
if (HifiSockAddr(&senderSockAddr) == session->_destinationSocket) {
// there is data for this session ready to be read
return 1;
} else {
// the next data from the dtlsSocket is not for this session
return 0;
}
} else {
// no data available on the dtlsSocket
return 0;
}
}
ssize_t DTLSSession::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;
HifiSockAddr pulledSockAddr;
qint64 bytesReceived = dtlsSocket.readDatagram(reinterpret_cast<char*>(buffer), size,
pulledSockAddr.getAddressPointer(), pulledSockAddr.getPortPointer());
if (bytesReceived == -1) {
// no data to pull, return -1
qDebug() << "Received no data on call to readDatagram";
return bytesReceived;
}
if (pulledSockAddr == session->_destinationSocket) {
// bytes received from the correct sender, return number of bytes received
qDebug() << "Received" << bytesReceived << "on DTLS socket from" << pulledSockAddr;
return bytesReceived;
} else {
qDebug() << pulledSockAddr << "is not" << session->_destinationSocket;
}
// we pulled a packet not matching this session, so output that
qDebug() << "Denied connection from" << pulledSockAddr;
gnutls_transport_set_errno(session->_gnutlsSession, GNUTLS_E_AGAIN);
return -1;
}
ssize_t DTLSSession::socketPush(gnutls_transport_ptr_t ptr, const void* buffer, size_t size) {
DTLSSession* session = static_cast<DTLSSession*>(ptr);
QUdpSocket& dtlsSocket = session->_dtlsSocket;
qDebug() << "Pushing a message of size" << size << "to" << session->_destinationSocket;
return dtlsSocket.writeDatagram(reinterpret_cast<const char*>(buffer), size,
session->_destinationSocket.getAddress(), session->_destinationSocket.getPort());
}
gnutls_certificate_credentials_t* DTLSSession::x509CACredentials() {
static gnutls_certificate_credentials_t x509Credentials;
static bool credentialsInitialized = false;
if (!credentialsInitialized) {
gnutls_certificate_allocate_credentials(&x509Credentials);
}
return &x509Credentials;
}
DTLSSession::DTLSSession(int end, QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket) :
_dtlsSocket(dtlsSocket),
_destinationSocket(destinationSocket)
{
gnutls_init(&_gnutlsSession, end | GNUTLS_DATAGRAM);
gnutls_init(&_gnutlsSession, end | GNUTLS_DATAGRAM | GNUTLS_NONBLOCK);
// see http://gnutls.org/manual/html_node/Datagram-TLS-API.html#gnutls_005fdtls_005fset_005fmtu
const unsigned int DTLS_MAX_MTU = 1452;
gnutls_dtls_set_mtu(_gnutlsSession, DTLS_MAX_MTU);
const unsigned int DTLS_TOTAL_CONNECTION_TIMEOUT = 10 * DOMAIN_SERVER_CHECK_IN_MSECS;
gnutls_dtls_set_timeouts(_gnutlsSession, 0, DTLS_TOTAL_CONNECTION_TIMEOUT);
gnutls_transport_set_ptr(_gnutlsSession, this);
gnutls_transport_set_push_function(_gnutlsSession, socketPush);
gnutls_transport_set_pull_function(_gnutlsSession, socketPull);
gnutls_transport_set_pull_timeout_function(_gnutlsSession, socketPullTimeout);
}

View file

@ -20,12 +20,13 @@ class DTLSSession : public QObject {
public:
DTLSSession(int end, QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket);
static gnutls_certificate_credentials_t* x509CACredentials();
protected:
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);
gnutls_session_t* getGnuTLSSession() { return &_gnutlsSession; }
protected:
QUdpSocket& _dtlsSocket;
gnutls_session_t _gnutlsSession;
HifiSockAddr _destinationSocket;

View file

@ -18,7 +18,6 @@ DomainHandler::DomainHandler() :
_sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
_assignmentUUID(),
_isConnected(false),
_requiresDTLS(false),
_dtlsSession(NULL)
{
@ -31,13 +30,18 @@ DomainHandler::~DomainHandler() {
void DomainHandler::clearConnectionInfo() {
_uuid = QUuid();
_isConnected = false;
delete _dtlsSession;
_dtlsSession = NULL;
}
void DomainHandler::reset() {
clearConnectionInfo();
_hostname = QString();
_sockAddr.setAddress(QHostAddress::Null);
_requiresDTLS = false;
delete _dtlsSession;
_dtlsSession = NULL;
}
void DomainHandler::initializeDTLSSession() {
@ -122,7 +126,6 @@ void DomainHandler::parseDTLSRequirementPacket(const QByteArray& dtlsRequirement
qDebug() << "domain-server DTLS port changed to" << dtlsPort << "- Enabling DTLS.";
_sockAddr.setPort(dtlsPort);
_requiresDTLS = true;
initializeDTLSSession();
}

View file

@ -49,6 +49,8 @@ public:
bool isConnected() const { return _isConnected; }
void setIsConnected(bool isConnected);
DTLSClientSession* getDTLSSession() { return _dtlsSession; }
void parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket);
private slots:
@ -66,7 +68,6 @@ private:
HifiSockAddr _sockAddr;
QUuid _assignmentUUID;
bool _isConnected;
bool _requiresDTLS;
DTLSClientSession* _dtlsSession;
};

View file

@ -50,9 +50,9 @@ HifiSockAddr::HifiSockAddr(const sockaddr* sockaddr) {
_address = QHostAddress(sockaddr);
if (sockaddr->sa_family == AF_INET) {
_port = reinterpret_cast<const sockaddr_in*>(sockaddr)->sin_port;
_port = ntohs(reinterpret_cast<const sockaddr_in*>(sockaddr)->sin_port);
} else {
_port = reinterpret_cast<const sockaddr_in6*>(sockaddr)->sin6_port;
_port = ntohs(reinterpret_cast<const sockaddr_in6*>(sockaddr)->sin6_port);
}
}

View file

@ -34,6 +34,8 @@
#include "DomainHandler.h"
#include "Node.h"
const int MAX_PACKET_SIZE = 1500;
const quint64 NODE_SILENCE_THRESHOLD_USECS = 2 * 1000 * 1000;
extern const char SOLO_NODE_TYPES[2];

View file

@ -12,6 +12,8 @@
#include <QtCore/QUrl>
#include <QtNetwork/QHostInfo>
#include <gnutls/dtls.h>
#include "AccountManager.h"
#include "Assignment.h"
#include "HifiSockAddr.h"
@ -318,39 +320,54 @@ void NodeList::sendDomainServerCheckIn() {
// send a STUN request to figure it out
sendSTUNRequest();
} else if (!_domainHandler.getIP().isNull()) {
// construct the DS check in packet
QUuid packetUUID = (!_sessionUUID.isNull() ? _sessionUUID : _domainHandler.getAssignmentUUID());
QByteArray domainServerPacket = byteArrayWithPopulatedHeader(PacketTypeDomainListRequest, packetUUID);
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
DTLSClientSession* dtlsSession = _domainHandler.getDTLSSession();
// pack our data to send to the domain-server
packetStream << _ownerType << _publicSockAddr
<< HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort())
<< (quint8) _nodeTypesOfInterest.size();
// copy over the bytes for node types of interest, if required
foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) {
packetStream << nodeTypeOfInterest;
}
writeDatagram(domainServerPacket, _domainHandler.getSockAddr(), QUuid());
const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5;
static unsigned int numDomainCheckins = 0;
// send a STUN request every Nth domain server check in so we update our public socket, if required
if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) {
sendSTUNRequest();
}
if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
// we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS
// so emit our signal that indicates that
emit limitOfSilentDomainCheckInsReached();
}
// increment the count of un-replied check-ins
_numNoReplyDomainCheckIns++;
if (dtlsSession) {
int handshakeReturn = gnutls_handshake(*dtlsSession->getGnuTLSSession());
gnutls_handshake_description_t inType = gnutls_handshake_get_last_in(*dtlsSession->getGnuTLSSession());
gnutls_handshake_description_t outType = gnutls_handshake_get_last_out(*dtlsSession->getGnuTLSSession());
qDebug() << "in" << gnutls_handshake_description_get_name(inType);
qDebug() << "out" << gnutls_handshake_description_get_name(outType);
// make sure DTLS handshake with the domain-server is complete
qDebug() << "GnuTLS handshake return is" << handshakeReturn;
} else {
// construct the DS check in packet
QUuid packetUUID = (!_sessionUUID.isNull() ? _sessionUUID : _domainHandler.getAssignmentUUID());
QByteArray domainServerPacket = byteArrayWithPopulatedHeader(PacketTypeDomainListRequest, packetUUID);
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
// pack our data to send to the domain-server
packetStream << _ownerType << _publicSockAddr
<< HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort())
<< (quint8) _nodeTypesOfInterest.size();
// copy over the bytes for node types of interest, if required
foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) {
packetStream << nodeTypeOfInterest;
}
writeDatagram(domainServerPacket, _domainHandler.getSockAddr(), QUuid());
const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5;
static unsigned int numDomainCheckins = 0;
// send a STUN request every Nth domain server check in so we update our public socket, if required
if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) {
sendSTUNRequest();
}
if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
// we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS
// so emit our signal that indicates that
emit limitOfSilentDomainCheckInsReached();
}
// increment the count of un-replied check-ins
_numNoReplyDomainCheckIns++;
}
}
}

View file

@ -35,7 +35,7 @@
#include "LimitedNodeList.h"
#include "Node.h"
const quint64 DOMAIN_SERVER_CHECK_IN_USECS = 1 * 1000000;
const quint64 DOMAIN_SERVER_CHECK_IN_MSECS = 1 * 1000;
const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5;

View file

@ -42,7 +42,6 @@ struct xColor {
unsigned char blue;
};
static const float ZERO = 0.0f;
static const float ONE = 1.0f;
static const float ONE_HALF = 0.5f;
@ -66,8 +65,6 @@ static const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND;
const int BITS_IN_BYTE = 8;
const int MAX_PACKET_SIZE = 1500;
quint64 usecTimestamp(const timeval *time);
quint64 usecTimestampNow();
void usecTimestampNowForceClockSkew(int clockSkew);

View file

@ -41,7 +41,7 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy
QTimer* domainServerTimer = new QTimer(this);
connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit()));
domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000);
domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
QTimer* silentNodeRemovalTimer = new QTimer(this);
connect(silentNodeRemovalTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));