DTLS-Server

Dieses Beispiel zeigt, wie ein einfacher DTLS-Server implementiert wird.

Screenshot des DTLS-Server-Beispiels.

Hinweis: Das DTLS-Server-Beispiel ist für die Ausführung zusammen mit dem DTLS-Client -Beispiel vorgesehen.

Der Server wird durch die Klasse `DtlsServer` implementiert. Er verwendet die Signale ` QUdpSocket`, ` QDtlsClientVerifier` und ` QDtls `, um die Erreichbarkeit der einzelnen Clients zu prüfen, einen Handshake durchzuführen sowie 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 Signal „ QUdpSocket::readyRead()“ mit seinem Slot „readyRead()“ und legt die minimal erforderliche TLS-Konfiguration fest:

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 nutzt einen Handshake mit vorab geteiltem Schlüssel (PSK).

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 Slot `readyRead()` 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);
    ...

Nach dem Extrahieren einer Adresse und einer Portnummer prüft der Server zunächst, ob es sich um ein Datagramm von einem bereits bekannten Peer 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 wurde:

    ...
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 den Handshake mit diesem Peer fort:

    ...
doHandshake(client->get(), dgram);
    ...

„handleNewConnection()“ überprüft, ob es sich um einen erreichbaren DTLS-Client handelt, oder sendet eine „HelloVerifyRequest“-Nachricht:

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"));
    ...

Wurde der neue Client als erreichbarer DTLS-Client verifiziert, 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);
    ...

doHandshake() durchläuft die Handshake-Phase:

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()“ ausgesendet, und der Slot „pskRequired()“ stellt den vorab geteilten Schlüssel bereit:

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ürzehalber wurde die Definition von `pskRequired()` stark vereinfacht. Die Dokumentation zur Klasse ` QSslPreSharedKeyAuthenticator ` erläutert ausführlich, wie dieser Slot korrekt implementiert werden kann.

Nachdem der Handshake für den Netzwerkpartner abgeschlossen ist, gilt eine verschlüsselte DTLS-Verbindung als hergestellt, und der Server entschlüsselt nachfolgende, vom Partner gesendete Datagramme durch Aufruf von `decryptDatagram()`. Der Server sendet außerdem eine verschlüsselte Antwort an den Partner:

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 von ` QDtls::shutdown()`:

void DtlsServer::shutdown()
{
    for (const auto &connection : std::exchange(knownClients, {}))
        connection->shutdown(&serverSocket);

    serverSocket.close();
}

Während des 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 Benutzeroberfläche 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));
}

Beispielprojekt @ code.qt.io

© 2026 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.