DTLS-Server
Dieses Beispiel zeigt, wie man einen einfachen DTLS-Server implementiert.
Hinweis: Das DTLS-Server-Beispiel soll zusammen mit dem DTLS-Client-Beispiel ausgeführt werden.
Der Server wird durch die Klasse DtlsServer implementiert. Er verwendet QUdpSocket, QDtlsClientVerifier und QDtls, um die Erreichbarkeit der einzelnen Clients zu testen, einen Handshake durchzuführen und verschlüsselte Nachrichten zu lesen und zu schreiben.
class DtlsServer : public QObject { Q_OBJECT public: DtlsServer(); ~DtlsServer(); bool listen(const QHostAddress &address, quint16 port); bool isListening() const; void close(); signals: void errorMessage(const QString &message); void warningMessage(const QString &message); void infoMessage(const QString &message); void datagramReceived(const QString &peerInfo, const QByteArray &cipherText, const QByteArray &plainText); private slots: void readyRead(); void pskRequired(QSslPreSharedKeyAuthenticator *auth); private: void handleNewConnection(const QHostAddress &peerAddress, quint16 peerPort, const QByteArray &clientHello); void doHandshake(QDtls *newConnection, const QByteArray &clientHello); void decryptDatagram(QDtls *connection, const QByteArray &clientMessage); void shutdown(); bool listening = false; QUdpSocket serverSocket; QSslConfiguration serverConfiguration; QDtlsClientVerifier cookieSender; std::vector<std::unique_ptr<QDtls>> knownClients; Q_DISABLE_COPY(DtlsServer) };
Der Konstruktor verbindet das QUdpSocket::readyRead() Signal mit seinem readyRead() Slot und setzt die minimal benötigte TLS Konfiguration:
DtlsServer::DtlsServer() { connect(&serverSocket, &QAbstractSocket::readyRead, this, &DtlsServer::readyRead); serverConfiguration = QSslConfiguration::defaultDtlsConfiguration(); serverConfiguration.setPreSharedKeyIdentityHint("Qt DTLS example server"); serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); }
Hinweis: Der Server verwendet kein Zertifikat und verlässt sich auf den Pre-Shared Key (PSK) Handshake.
listen() bindet QUdpSocket:
bool DtlsServer::listen(const QHostAddress &address, quint16 port) { if (address != serverSocket.localAddress() || port != serverSocket.localPort()) { shutdown(); listening = serverSocket.bind(address, port); if (!listening) emit errorMessage(serverSocket.errorString()); } else { listening = true; } return listening; }
Der readyRead()-Slot verarbeitet eingehende Datagramme:
... const qint64 bytesToRead = serverSocket.pendingDatagramSize(); if (bytesToRead <= 0) { emit warningMessage(tr("A spurious read notification")); return; } QByteArray dgram(bytesToRead, Qt::Uninitialized); QHostAddress peerAddress; quint16 peerPort = 0; const qint64 bytesRead = serverSocket.readDatagram(dgram.data(), dgram.size(), &peerAddress, &peerPort); if (bytesRead <= 0) { emit warningMessage(tr("Failed to read a datagram: ") + serverSocket.errorString()); return; } dgram.resize(bytesRead); ...
Nachdem er eine Adresse und eine Portnummer extrahiert hat, prüft der Server zunächst, ob es sich um ein Datagramm von einer bereits bekannten Gegenstelle handelt:
... if (peerAddress.isNull() || !peerPort) { emit warningMessage(tr("Failed to extract peer info (address, port)")); return; } const auto client = std::find_if(knownClients.begin(), knownClients.end(), [&](const std::unique_ptr<QDtls> &connection){ return connection->peerAddress() == peerAddress && connection->peerPort() == peerPort; }); ...
Handelt es sich um eine neue, unbekannte Adresse und einen unbekannten Port, wird das Datagramm als potenzielle ClientHello-Nachricht verarbeitet, die von einem DTLS-Client gesendet wird:
... if (client == knownClients.end()) return handleNewConnection(peerAddress, peerPort, dgram); ...
Handelt es sich um einen bekannten DTLS-Client, entschlüsselt der Server entweder das Datagramm:
... if ((*client)->isConnectionEncrypted()) { decryptDatagram(client->get(), dgram); if ((*client)->dtlsError() == QDtlsError::RemoteClosedConnectionError) knownClients.erase(client); return; } ...
oder setzt einen Handshake mit dieser Gegenstelle fort:
... doHandshake(client->get(), dgram); ...
handleNewConnection() überprüft, ob es sich um einen erreichbaren DTLS-Client handelt, oder sendet eine HelloVerifyRequest:
void DtlsServer::handleNewConnection(const QHostAddress &peerAddress, quint16 peerPort, const QByteArray &clientHello) { if (!listening) return; const QString peerInfo = peer_info(peerAddress, peerPort); if (cookieSender.verifyClient(&serverSocket, clientHello, peerAddress, peerPort)) { emit infoMessage(peerInfo + tr(": verified, starting a handshake")); ...
Wenn der neue Client als erreichbarer DTLS-Client verifiziert wurde, erstellt und konfiguriert der Server ein neues QDtls Objekt und startet einen serverseitigen Handshake:
... std::unique_ptr<QDtls> newConnection{new QDtls{QSslSocket::SslServerMode}}; newConnection->setDtlsConfiguration(serverConfiguration); newConnection->setPeer(peerAddress, peerPort); newConnection->connect(newConnection.get(), &QDtls::pskRequired, this, &DtlsServer::pskRequired); knownClients.push_back(std::move(newConnection)); doHandshake(knownClients.back().get(), clientHello); ...
Mit doHandshake() wird die Handshake-Phase durchlaufen:
void DtlsServer::doHandshake(QDtls *newConnection, const QByteArray &clientHello) { const bool result = newConnection->doHandshake(&serverSocket, clientHello); if (!result) { emit errorMessage(newConnection->dtlsErrorString()); return; } const QString peerInfo = peer_info(newConnection->peerAddress(), newConnection->peerPort()); switch (newConnection->handshakeState()) { case QDtls::HandshakeInProgress: emit infoMessage(peerInfo + tr(": handshake is in progress ...")); break; case QDtls::HandshakeComplete: emit infoMessage(tr("Connection with %1 encrypted. %2") .arg(peerInfo, connection_info(newConnection))); break; default: Q_UNREACHABLE(); } }
Während der Handshake-Phase wird das Signal QDtls::pskRequired() ausgegeben, und der Slot pskRequired() liefert den Pre-Shared Key:
void DtlsServer::pskRequired(QSslPreSharedKeyAuthenticator *auth) { Q_ASSERT(auth); emit infoMessage(tr("PSK callback, received a client's identity: '%1'") .arg(QString::fromLatin1(auth->identity()))); auth->setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f")); }
Hinweis: Der Kürze halber ist die Definition von pskRequired() stark vereinfacht. In der Dokumentation zur Klasse QSslPreSharedKeyAuthenticator wird ausführlich erläutert, wie dieser Slot richtig implementiert werden kann.
Nachdem der Handshake für den Netzwerk-Peer abgeschlossen ist, gilt eine verschlüsselte DTLS-Verbindung als hergestellt, und der Server entschlüsselt die vom Peer gesendeten Datagramme durch den Aufruf von decryptDatagram(). Der Server sendet auch eine verschlüsselte Antwort an die Gegenstelle:
void DtlsServer::decryptDatagram(QDtls *connection, const QByteArray &clientMessage) { Q_ASSERT(connection->isConnectionEncrypted()); const QString peerInfo = peer_info(connection->peerAddress(), connection->peerPort()); const QByteArray dgram = connection->decryptDatagram(&serverSocket, clientMessage); if (dgram.size()) { emit datagramReceived(peerInfo, clientMessage, dgram); connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1()); } else if (connection->dtlsError() == QDtlsError::NoError) { emit warningMessage(peerInfo + ": " + tr("0 byte dgram, could be a re-connect attempt?")); } else { emit errorMessage(peerInfo + ": " + connection->dtlsErrorString()); } }
Der Server schließt seine DTLS-Verbindungen durch den Aufruf QDtls::shutdown():
void DtlsServer::shutdown() { for (const auto &connection : std::exchange(knownClients, {})) connection->shutdown(&serverSocket); serverSocket.close(); }
Während seines Betriebs meldet der Server Fehler, Informationsmeldungen und entschlüsselte Datagramme, indem er die Signale errorMessage(), warningMessage(), infoMessage() und datagramReceived() aussendet. Diese Meldungen werden von der UI des Servers protokolliert:
const QString colorizer(QStringLiteral("<font color=\"%1\">%2</font><br>")); void MainWindow::addErrorMessage(const QString &message) { ui->serverInfo->insertHtml(colorizer.arg(QStringLiteral("Crimson"), message)); } void MainWindow::addWarningMessage(const QString &message) { ui->serverInfo->insertHtml(colorizer.arg(QStringLiteral("DarkOrange"), message)); } void MainWindow::addInfoMessage(const QString &message) { ui->serverInfo->insertHtml(colorizer.arg(QStringLiteral("DarkBlue"), message)); } void MainWindow::addClientMessage(const QString &peerInfo, const QByteArray &datagram, const QByteArray &plainText) { static const QString messageColor = QStringLiteral("DarkMagenta"); static const QString formatter = QStringLiteral("<br>---------------" "<br>A message from %1" "<br>DTLS datagram:<br> %2" "<br>As plain text:<br> %3"); const QString html = formatter.arg(peerInfo, QString::fromUtf8(datagram.toHex(' ')), QString::fromUtf8(plainText)); ui->messages->insertHtml(colorizer.arg(messageColor, html)); }
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.