DTLS-Client
Dieses Beispiel veranschaulicht, wie clientseitige DTLS-Verbindungen implementiert werden können.
Hinweis: Das DTLS-Client-Beispiel sollte zusammen mit dem DTLS-Server-Beispiel ausgeführt werden.
Der Beispiel-DTLS-Client kann mehrere DTLS-Verbindungen zu einem oder mehreren DTLS-Servern herstellen. Eine clientseitige DTLS-Verbindung wird durch die Klasse DtlsAssociation implementiert. Diese Klasse verwendet QUdpSocket zum Lesen und Schreiben von Datagrammen und QDtls zur Verschlüsselung:
class DtlsAssociation : public QObject { Q_OBJECT public: DtlsAssociation(const QHostAddress &address, quint16 port, const QString &connectionName); ~DtlsAssociation(); void startHandshake(); signals: void errorMessage(const QString &message); void warningMessage(const QString &message); void infoMessage(const QString &message); void serverResponse(const QString &clientInfo, const QByteArray &datagraam, const QByteArray &plainText); private slots: void udpSocketConnected(); void readyRead(); void handshakeTimeout(); void pskRequired(QSslPreSharedKeyAuthenticator *auth); void pingTimeout(); private: QString name; QUdpSocket socket; QDtls crypto; QTimer pingTimer; unsigned ping = 0; Q_DISABLE_COPY(DtlsAssociation) };
Der Konstruktor setzt die minimale TLS-Konfiguration für die neue DTLS-Verbindung und setzt die Adresse und den Port des Servers:
... auto configuration = QSslConfiguration::defaultDtlsConfiguration(); configuration.setPeerVerifyMode(QSslSocket::VerifyNone); crypto.setPeer(address, port); crypto.setDtlsConfiguration(configuration); ...
Das Signal QDtls::handshakeTimeout() ist mit dem Slot handleTimeout() verbunden, um Paketverluste und erneute Übertragungen während der Handshake-Phase zu behandeln:
... connect(&crypto, &QDtls::handshakeTimeout, this, &DtlsAssociation::handshakeTimeout); ...
Um sicherzustellen, dass wir nur die Datagramme des Servers empfangen, verbinden wir unseren UDP-Socket mit dem Server:
... socket.connectToHost(address.toString(), port); ...
Das Signal QUdpSocket::readyRead() ist mit dem readyRead()-Slot verbunden:
... connect(&socket, &QUdpSocket::readyRead, this, &DtlsAssociation::readyRead); ...
Wenn eine sichere Verbindung zu einem Server hergestellt ist, sendet ein DtlsAssociation-Objekt kurze Ping-Nachrichten an den Server, wobei ein Timer verwendet wird:
pingTimer.setInterval(5000); connect(&pingTimer, &QTimer::timeout, this, &DtlsAssociation::pingTimeout);
startHandshake() startet einen Handshake mit dem Server:
void DtlsAssociation::startHandshake() { if (socket.state() != QAbstractSocket::ConnectedState) { emit infoMessage(tr("%1: connecting UDP socket first ...").arg(name)); connect(&socket, &QAbstractSocket::connected, this, &DtlsAssociation::udpSocketConnected); return; } if (!crypto.doHandshake(&socket)) emit errorMessage(tr("%1: failed to start a handshake - %2").arg(name, crypto.dtlsErrorString())); else emit infoMessage(tr("%1: starting a handshake").arg(name)); }
Der readyRead()-Slot liest ein vom Server gesendetes Datagramm:
QByteArray dgram(socket.pendingDatagramSize(), Qt::Uninitialized); const qint64 bytesRead = socket.readDatagram(dgram.data(), dgram.size()); if (bytesRead <= 0) { emit warningMessage(tr("%1: spurious read notification?").arg(name)); return; } dgram.resize(bytesRead);
Wenn der Handshake bereits abgeschlossen wurde, wird dieses Datagramm entschlüsselt:
if (crypto.isConnectionEncrypted()) { const QByteArray plainText = crypto.decryptDatagram(&socket, dgram); if (plainText.size()) { emit serverResponse(name, dgram, plainText); return; } if (crypto.dtlsError() == QDtlsError::RemoteClosedConnectionError) { emit errorMessage(tr("%1: shutdown alert received").arg(name)); socket.close(); pingTimer.stop(); return; } emit warningMessage(tr("%1: zero-length datagram received?").arg(name)); } else {
Andernfalls wird versucht, den Handshake fortzusetzen:
if (!crypto.doHandshake(&socket, dgram)) { emit errorMessage(tr("%1: handshake error - %2").arg(name, crypto.dtlsErrorString())); return; }
Wenn der Handshake abgeschlossen ist, senden wir unsere erste Ping-Nachricht:
if (crypto.isConnectionEncrypted()) { emit infoMessage(tr("%1: encrypted connection established!").arg(name)); pingTimer.start(); pingTimeout(); } else {
Der Slot pskRequired() liefert den Pre-Shared Key (PSK), der während der Handshake-Phase benötigt wird:
void DtlsAssociation::pskRequired(QSslPreSharedKeyAuthenticator *auth) { Q_ASSERT(auth); emit infoMessage(tr("%1: providing pre-shared key ...").arg(name)); auth->setIdentity(name.toLatin1()); auth->setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f")); }
Hinweis: Der Kürze halber ist die Definition von pskRequired() stark vereinfacht. Die Dokumentation der Klasse QSslPreSharedKeyAuthenticator erklärt im Detail, wie dieser Slot richtig implementiert werden kann.
pingTimeout() sendet eine verschlüsselte Nachricht an den Server:
void DtlsAssociation::pingTimeout() { static const QString message = QStringLiteral("I am %1, please, accept our ping %2"); const qint64 written = crypto.writeDatagramEncrypted(&socket, message.arg(name).arg(ping).toLatin1()); if (written <= 0) { emit errorMessage(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())); pingTimer.stop(); return; } ++ping; }
Während der Handshake-Phase muss der Client mit möglichen Timeouts umgehen, die aufgrund von Paketverlusten auftreten können. Der handshakeTimeout()-Slot überträgt die Handshake-Nachrichten erneut:
void DtlsAssociation::handshakeTimeout() { emit warningMessage(tr("%1: handshake timeout, trying to re-transmit").arg(name)); if (!crypto.handleTimeout(&socket)) emit errorMessage(tr("%1: failed to re-transmit - %2").arg(name, crypto.dtlsErrorString())); }
Bevor eine Client-Verbindung abgebaut wird, muss ihre DTLS-Verbindung beendet werden:
DtlsAssociation::~DtlsAssociation() { if (crypto.isConnectionEncrypted()) crypto.shutdown(&socket); }
Fehlermeldungen, Informationsmeldungen und entschlüsselte Antworten von Servern werden auf der Benutzeroberfläche angezeigt:
const QString colorizer(QStringLiteral("<font color=\"%1\">%2</font><br>")); void MainWindow::addErrorMessage(const QString &message) { ui->clientMessages->insertHtml(colorizer.arg(QStringLiteral("Crimson"), message)); } void MainWindow::addWarningMessage(const QString &message) { ui->clientMessages->insertHtml(colorizer.arg(QStringLiteral("DarkOrange"), message)); } void MainWindow::addInfoMessage(const QString &message) { ui->clientMessages->insertHtml(colorizer.arg(QStringLiteral("DarkBlue"), message)); } void MainWindow::addServerResponse(const QString &clientInfo, const QByteArray &datagram, const QByteArray &plainText) { static const QString messageColor = QStringLiteral("DarkMagenta"); static const QString formatter = QStringLiteral("<br>---------------" "<br>%1 received a DTLS datagram:<br> %2" "<br>As plain text:<br> %3"); const QString html = formatter.arg(clientInfo, QString::fromUtf8(datagram.toHex(' ')), QString::fromUtf8(plainText)); ui->serverMessages->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.