DTLS サーバー

この例では、簡単な DTLS サーバーの実装方法を示します。

DTLSサーバーの例のスクリーンショット。

注:この DTLSサーバーのサンプルは 、DTLSクライアントのサンプルと併せて実行することを想定しています。

サーバーは DtlsServer クラスによって実装されています。このクラスは、QUdpSocket 、QDtlsClientVerifier 、およびQDtls を使用して、各クライアントへの到達可能性をテストし、ハンドシェイクを完了させ、暗号化されたメッセージの読み取りと書き込みを行います。

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)
};

コンストラクタでは、QUdpSocket::readyRead() シグナルを readyRead() スロットに接続し、必要最小限の TLS 設定を行います:

DtlsServer::DtlsServer()
{
    connect(&serverSocket, &QAbstractSocket::readyRead, this, &DtlsServer::readyRead);

    serverConfiguration = QSslConfiguration::defaultDtlsConfiguration();
    serverConfiguration.setPreSharedKeyIdentityHint("Qt DTLS example server");
    serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
}

注:このサーバーは 証明書を使用せず、事前共有鍵(PSK)によるハンドシェイクに依存しています。

listen() は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;
}

readyRead()スロットは、受信したデータグラムを処理します:

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

アドレスとポート番号を抽出後、サーバーはまず、それが既知のピアからのデータグラムであるかどうかを検証します:

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

新しい未知のアドレスとポートの場合、そのデータグラムはDTLSクライアントから送信された潜在的なClientHelloメッセージとして処理されます:

    ...
if (client == knownClients.end())
    return handleNewConnection(peerAddress, peerPort, dgram);
    ...

既知のDTLSクライアントである場合、サーバーはデータグラムを復号するか:

    ...
if ((*client)->isConnectionEncrypted()) {
    decryptDatagram(client->get(), dgram);
    if ((*client)->dtlsError() == QDtlsError::RemoteClosedConnectionError)
        knownClients.erase(client);
    return;
}
    ...

、あるいはこのピアとのハンドシェイクを継続します:

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

handleNewConnection() は、そのクライアントが到達可能な DTLS クライアントであることを確認するか、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"));
    ...

新しいクライアントが到達可能なDTLSクライアントであると確認された場合、サーバーは新しいQDtls オブジェクトを作成・設定し、サーバー側のハンドシェイクを開始します:

    ...
    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()はハンドシェイクフェーズを進めます:

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();
    }
}

ハンドシェイクフェーズ中、QDtls::pskRequired()シグナルが発信され、pskRequired()スロットが事前共有鍵を提供します:

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

注: 簡潔さを重視するため 、pskRequired() の定義は過度に単純化されています。QSslPreSharedKeyAuthenticator クラスのドキュメントには、このスロットを適切に実装する方法が詳しく説明されています。

ネットワーク相手先とのハンドシェイクが完了すると、暗号化された DTLS 接続が確立されたものとみなされ、サーバーは decryptDatagram() を呼び出すことで、相手先から送信されるその後のデータグラムを復号します。また、サーバーは相手先に暗号化された応答を送信します:

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());
    }
}

サーバーは、QDtls::shutdown() を呼び出すことで DTLS 接続を閉じます:

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

    serverSocket.close();
}

サーバーは動作中、シグナル errorMessage()、warningMessage()、infoMessage()、および datagramReceived() を発行することで、エラー、情報メッセージ、および復号されたデータグラムを報告します。これらのメッセージは、サーバーの UI によってログに記録されます:

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));
}

サンプルプロジェクト @ 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.