DTLS client▲
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 : public
QObject
{
Q_OBJECT
public
:
DtlsAssociation(const
QHostAddress &
amp;address, quint16 port,
const
QString &
amp;connectionName);
~
DtlsAssociation();
void
startHandshake();
signals
:
void
errorMessage(const
QString &
amp;message);
void
warningMessage(const
QString &
amp;message);
void
infoMessage(const
QString &
amp;message);
void
serverResponse(const
QString &
amp;clientInfo, const
QByteArray &
amp;datagraam,
const
QByteArray &
amp;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)
}
;
The constructor sets the minimal TLS configuration for the new DTLS connection, and sets the address and the port of the server:
...
auto
configuration =
QSslConfiguration::
defaultDtlsConfiguration();
configuration.setPeerVerifyMode(QSslSocket::
VerifyNone);
crypto.setPeer(address, port);
crypto.setDtlsConfiguration(configuration);
...
The QDtls::handshakeTimeout() signal is connected to the handleTimeout() slot to deal with packet loss and retransmission during the handshake phase:
...
connect(&
amp;crypto, &
amp;QDtls::
handshakeTimeout, this
, &
amp;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 QUdpSocket::readyRead() signal is connected to the readyRead() slot:
...
connect(&
amp;socket, &
amp;QUdpSocket::
readyRead, this
, &
amp;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(&
amp;pingTimer, &
amp;QTimer::
timeout, this
, &
amp;DtlsAssociation::
pingTimeout);
startHandshake() starts a handshake with the server:
void
DtlsAssociation::
startHandshake()
{
if
(socket.state() !=
QAbstractSocket::
ConnectedState) {
emit infoMessage(tr("%1: connecting UDP socket first ..."
).arg(name));
connect(&
amp;socket, &
amp;QAbstractSocket::
connected, this
, &
amp;DtlsAssociation::
udpSocketConnected);
return
;
}
if
(!
crypto.doHandshake(&
amp;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));
}
The readyRead() slot reads a datagram sent by the server:
QByteArray dgram(socket.pendingDatagramSize(), Qt::
Uninitialized);
const
qint64 bytesRead =
socket.readDatagram(dgram.data(), dgram.size());
if
(bytesRead &
lt;=
0
) {
emit warningMessage(tr("%1: spurious read notification?"
).arg(name));
return
;
}
dgram.resize(bytesRead);
If the handshake was already completed, this datagram is decrypted:
if
(crypto.isConnectionEncrypted()) {
const
QByteArray plainText =
crypto.decryptDatagram(&
amp;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
{
otherwise, we try to continue the handshake:
if
(!
crypto.doHandshake(&
amp;socket, dgram)) {
emit errorMessage(tr("%1: handshake error - %2"
).arg(name, crypto.dtlsErrorString()));
return
;
}
When the handshake has completed, we send our first ping message:
if
(crypto.isConnectionEncrypted()) {
emit infoMessage(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:
void
DtlsAssociation::
pskRequired(QSslPreSharedKeyAuthenticator *
auth)
{
Q_ASSERT(auth);
emit infoMessage(tr("%1: providing pre-shared key ..."
).arg(name));
auth-&
gt;setIdentity(name.toLatin1());
auth-&
gt;setPreSharedKey(QByteArrayLiteral("
\x1a\x2b\x3c\x4d\x5e\x6f
"
));
}
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:
void
DtlsAssociation::
pingTimeout()
{
static
const
QString message =
QStringLiteral("I am %1, please, accept our ping %2"
);
const
qint64 written =
crypto.writeDatagramEncrypted(&
amp;socket, message.arg(name).arg(p