DTLS client¶
This example demonstrates how to implement client-side DTLS connections.
Note
The DTLS client example is intended to be run alongside the DTLS server example.
The example DTLS client can establish several DTLS connections to one or many DTLS servers. A client-side DTLS connection is implemented by the DtlsAssociation class. This class uses QUdpSocket
to read and write datagrams and QDtls
for encryption:
class DtlsAssociation(QObject): Q_OBJECT # public DtlsAssociation(QHostAddress address, quint16 port, connectionName) = QString() ~DtlsAssociation() def startHandshake(): signals: def errorMessage(message): def warningMessage(message): def infoMessage(message): def serverResponse(clientInfo, datagraam,): plainText) = QByteArray() slots: = private() def udpSocketConnected(): def readyRead(): def handshakeTimeout(): def pskRequired(auth): def pingTimeout(): # private name = QString() socket = QUdpSocket() crypto = QDtls() pingTimer = QTimer() ping = 0 Q_DISABLE_COPY(DtlsAssociation)
The constructor sets the minimal TLS configuration for the new DTLS connection, and sets the address and the port of the server:
... configuration = QSslConfiguration.defaultDtlsConfiguration() configuration.setPeerVerifyMode(QSslSocket.VerifyNone) crypto.setPeer(address, port) crypto.setDtlsConfiguration(configuration) ...
The handshakeTimeout()
signal is connected to the handleTimeout() slot to deal with packet loss and retransmission during the handshake phase:
... connect(crypto, QDtls.handshakeTimeout, self, DtlsAssociation.handshakeTimeout) ...
To ensure we receive only the datagrams from the server, we connect our UDP socket to the server:
... socket.connectToHost(address.toString(), port) ...
The readyRead()
signal is connected to the readyRead() slot:
... connect(socket, QUdpSocket.readyRead, self, DtlsAssociation.readyRead) ...
When a secure connection to a server is established, a DtlsAssociation object will be sending short ping messages to the server, using a timer:
pingTimer.setInterval(5000) connect(pingTimer, QTimer.timeout, self, DtlsAssociation.pingTimeout)
startHandshake() starts a handshake with the server:
def startHandshake(self): if (socket.state() != QAbstractSocket.ConnectedState) { infoMessage.emit(tr("%1: connecting UDP socket first ...").arg(name)) connect(socket, QAbstractSocket.connected, self, DtlsAssociation.udpSocketConnected) return if (not crypto.doHandshake(socket)) errorMessage.emit(tr("%1: failed to start a handshake - %2").arg(name, crypto.dtlsErrorString())) else: infoMessage.emit(tr("%1: starting a handshake").arg(name))
The readyRead() slot reads a datagram sent by the server:
dgram = QByteArray(socket.pendingDatagramSize(), Qt.Uninitialized) bytesRead = socket.readDatagram(dgram.data(), dgram.size()) if (bytesRead <= 0) { warningMessage.emit(tr("%1: spurious read notification?").arg(name)) return dgram.resize(bytesRead)
If the handshake was already completed, this datagram is decrypted:
if (crypto.isConnectionEncrypted()) { plainText = crypto.decryptDatagram(socket, dgram) if (plainText.size()) { serverResponse.emit(name, dgram, plainText) return if (crypto.dtlsError() == QDtlsError.RemoteClosedConnectionError) { errorMessage.emit(tr("%1: shutdown alert received").arg(name)) socket.close() pingTimer.stop() return warningMessage.emit(tr("%1: zero-length datagram received?").arg(name)) else:
otherwise, we try to continue the handshake:
if (not crypto.doHandshake(socket, dgram)) { errorMessage.emit(tr("%1: handshake error - %2").arg(name, crypto.dtlsErrorString())) return
When the handshake has completed, we send our first ping message:
if (crypto.isConnectionEncrypted()) { infoMessage.emit(tr("%1: encrypted connection established!").arg(name)) pingTimer.start() pingTimeout() else:
The pskRequired() slot provides the Pre-Shared Key (PSK) needed during the handshake phase:
def pskRequired(self, auth): Q_ASSERT(auth) infoMessage.emit(tr("%1: providing pre-shared key ...").arg(name)) auth.setIdentity(name.toLatin1()) auth.setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f"))
Note
For the sake of brevity, the definition of pskRequired() is oversimplified. The documentation for the QSslPreSharedKeyAuthenticator
class explains in detail how this slot can be properly implemented.
pingTimeout() sends an encrypted message to the server:
def pingTimeout(self): message = QStringLiteral("I am %1, please, accept our ping %2") written = crypto.writeDatagramEncrypted(socket, message.arg(name).arg(ping).toLatin1()) if (written <= 0) { errorMessage.emit(tr("%1: failed to send a ping - %2").arg(name, crypto.dtlsErrorString())) pingTimer.stop() return ping = ping + 1
During the handshake phase the client must handle possible timeouts, which can happen due to packet loss. The handshakeTimeout() slot retransmits the handshake messages:
def handshakeTimeout(self): warningMessage.emit(tr("%1: handshake timeout, trying to re-transmit").arg(name)) if (not crypto.handleTimeout(socket)) errorMessage.emit(tr("%1: failed to re-transmit - %2").arg(name, crypto.dtlsErrorString()))
Before a client connection is destroyed, its DTLS connection must be shut down:
DtlsAssociation::~DtlsAssociation() if (crypto.isConnectionEncrypted()) crypto.shutdown(socket)
Error messages, informational messages, and decrypted responses from servers are displayed by the UI:
colorizer = QString(QStringLiteral("<font color=\"%1\">%2")) def addErrorMessage(self, message): ui.clientMessages.insertHtml(colorizer.arg(QStringLiteral("Crimson"), message)) def addWarningMessage(self, message): ui.clientMessages.insertHtml(colorizer.arg(QStringLiteral("DarkOrange"), message)) def addInfoMessage(self, message): ui.clientMessages.insertHtml(colorizer.arg(QStringLiteral("DarkBlue"), message)) def addServerResponse(self, clientInfo, datagram,): plainText) = QByteArray() messageColor = QStringLiteral("DarkMagenta") formatter = QStringLiteral("<br>---------------"() "<br>%1 received a DTLS datagram:<br> %2" "<br>As plain text:<br> %3") html = formatter.arg(clientInfo, QString.fromUtf8(datagram.toHex(' ')), QString.fromUtf8(plainText)) ui.serverMessages.insertHtml(colorizer.arg(messageColor, html))
© 2022 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.