Qt文档阅读笔记-DTLS server解析

傷城~ 2022-10-05 08:50 279阅读 0赞

此篇博文展示了如何创建一个简单的DTLS服务端:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxNzg0NDI3NjE_size_16_color_FFFFFF_t_70

注意:这个DTLS服务端需要和DTLS客户端一起跑,才能看出效果。

服务端实现了DtlsServer类,这个类使用了QUdpSocket,QDtlsClientVerifier,QDtls使用这些类用于和客户端的连接,完成握手及数据加密传输与读取。

  1. class DtlsServer : public QObject
  2. {
  3. Q_OBJECT
  4. public:
  5. DtlsServer();
  6. ~DtlsServer();
  7. bool listen(const QHostAddress &address, quint16 port);
  8. bool isListening() const;
  9. void close();
  10. signals:
  11. void errorMessage(const QString &message);
  12. void warningMessage(const QString &message);
  13. void infoMessage(const QString &message);
  14. void datagramReceived(const QString &peerInfo, const QByteArray &cipherText,
  15. const QByteArray &plainText);
  16. private slots:
  17. void readyRead();
  18. void pskRequired(QSslPreSharedKeyAuthenticator *auth);
  19. private:
  20. void handleNewConnection(const QHostAddress &peerAddress, quint16 peerPort,
  21. const QByteArray &clientHello);
  22. void doHandshake(QDtls *newConnection, const QByteArray &clientHello);
  23. void decryptDatagram(QDtls *connection, const QByteArray &clientMessage);
  24. void shutdown();
  25. bool listening = false;
  26. QUdpSocket serverSocket;
  27. QSslConfiguration serverConfiguration;
  28. QDtlsClientVerifier cookieSender;
  29. std::vector<std::unique_ptr<QDtls>> knownClients;
  30. Q_DISABLE_COPY(DtlsServer)
  31. };

构造函数中QUdpSocket::readyRead()信号连接了readyRead()槽,用于获取客户端的数据报,最简单的设置了DTLS的配置:

  1. DtlsServer::DtlsServer()
  2. {
  3. connect(&serverSocket, &QAbstractSocket::readyRead, this, &DtlsServer::readyRead);
  4. serverConfiguration = QSslConfiguration::defaultDtlsConfiguration();
  5. serverConfiguration.setPreSharedKeyIdentityHint("Qt DTLS example server");
  6. serverConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
  7. }

注意:服务端未使用证书,仅仅是依赖共享密钥(PSK)握手。

listen()与QUdpSocket进行了捆绑:

  1. bool DtlsServer::listen(const QHostAddress &address, quint16 port)
  2. {
  3. if (address != serverSocket.localAddress() || port != serverSocket.localPort()) {
  4. shutdown();
  5. listening = serverSocket.bind(address, port);
  6. if (!listening)
  7. emit errorMessage(serverSocket.errorString());
  8. } else {
  9. listening = true;
  10. }
  11. return listening;
  12. }

readyRead()槽函数获取客户端数据报:

  1. ...
  2. const qint64 bytesToRead = serverSocket.pendingDatagramSize();
  3. if (bytesToRead <= 0) {
  4. emit warningMessage(tr("A spurious read notification"));
  5. return;
  6. }
  7. QByteArray dgram(bytesToRead, Qt::Uninitialized);
  8. QHostAddress peerAddress;
  9. quint16 peerPort = 0;
  10. const qint64 bytesRead = serverSocket.readDatagram(dgram.data(), dgram.size(),
  11. &peerAddress, &peerPort);
  12. if (bytesRead <= 0) {
  13. emit warningMessage(tr("Failed to read a datagram: ") + serverSocket.errorString());
  14. return;
  15. }
  16. dgram.resize(bytesRead);
  17. ...

随后获取客户端的IP和端口,服务端发送数据报给客户端:

  1. ...
  2. if (peerAddress.isNull() || !peerPort) {
  3. emit warningMessage(tr("Failed to extract peer info (address, port)"));
  4. return;
  5. }
  6. const auto client = std::find_if(knownClients.begin(), knownClients.end(),
  7. [&](const std::unique_ptr<QDtls> &connection){
  8. return connection->peerAddress() == peerAddress
  9. && connection->peerPort() == peerPort;
  10. });
  11. ...

如果是新的客户端,未知地址和端口,客户端会发送ClientHello消息,随后服务端回HelloVerifyRequest这个要先处理下:

  1. ...
  2. if (client == knownClients.end())
  3. return handleNewConnection(peerAddress, peerPort, dgram);
  4. ...

如果是连上的已知的客户端,服务端会进行解密:

  1. ...
  2. if ((*client)->isConnectionEncrypted()) {
  3. decryptDatagram(client->get(), dgram);
  4. if ((*client)->dtlsError() == QDtlsError::RemoteClosedConnectionError)
  5. knownClients.erase(client);
  6. return;
  7. }
  8. ...

以及进行握手:

  1. ...
  2. doHandshake(client->get(), dgram);
  3. ...

handleNewConnect()对客户端进行认证,以及发送HelloVerifyRequest:

  1. void DtlsServer::handleNewConnection(const QHostAddress &peerAddress,
  2. quint16 peerPort, const QByteArray &clientHello)
  3. {
  4. if (!listening)
  5. return;
  6. const QString peerInfo = peer_info(peerAddress, peerPort);
  7. if (cookieSender.verifyClient(&serverSocket, clientHello, peerAddress, peerPort)) {
  8. emit infoMessage(peerInfo + tr(": verified, starting a handshake"));
  9. ...

如果服务端认为新来的客户端可达,那个服务端会创建一个Qtls的配置,然后进行握手:

  1. ...
  2. std::unique_ptr<QDtls> newConnection{new QDtls{QSslSocket::SslServerMode}};
  3. newConnection->setDtlsConfiguration(serverConfiguration);
  4. newConnection->setPeer(peerAddress, peerPort);
  5. newConnection->connect(newConnection.get(), &QDtls::pskRequired,
  6. this, &DtlsServer::pskRequired);
  7. knownClients.push_back(std::move(newConnection));
  8. doHandshake(knownClients.back().get(), clientHello);
  9. ...

关于握手的代码在doHandshake()中

  1. void DtlsServer::doHandshake(QDtls *newConnection, const QByteArray &clientHello)
  2. {
  3. const bool result = newConnection->doHandshake(&serverSocket, clientHello);
  4. if (!result) {
  5. emit errorMessage(newConnection->dtlsErrorString());
  6. return;
  7. }
  8. const QString peerInfo = peer_info(newConnection->peerAddress(),
  9. newConnection->peerPort());
  10. switch (newConnection->handshakeState()) {
  11. case QDtls::HandshakeInProgress:
  12. emit infoMessage(peerInfo + tr(": handshake is in progress ..."));
  13. break;
  14. case QDtls::HandshakeComplete:
  15. emit infoMessage(tr("Connection with %1 encrypted. %2")
  16. .arg(peerInfo, connection_info(newConnection)));
  17. break;
  18. default:
  19. Q_UNREACHABLE();
  20. }
  21. }

在握手中,QDtls::pskRequired()信号关联到pskRequired()槽函数中,并且设置了PSK:

  1. void DtlsServer::pskRequired(QSslPreSharedKeyAuthenticator *auth)
  2. {
  3. Q_ASSERT(auth);
  4. emit infoMessage(tr("PSK callback, received a client's identity: '%1'")
  5. .arg(QString::fromLatin1(auth->identity())));
  6. auth->setPreSharedKey(QByteArrayLiteral("\x1a\x2b\x3c\x4d\x5e\x6f"));
  7. }

在握手完成后,就可以进行数据的加密发送和响应了:

  1. void DtlsServer::decryptDatagram(QDtls *connection, const QByteArray &clientMessage)
  2. {
  3. Q_ASSERT(connection->isConnectionEncrypted());
  4. const QString peerInfo = peer_info(connection->peerAddress(), connection->peerPort());
  5. const QByteArray dgram = connection->decryptDatagram(&serverSocket, clientMessage);
  6. if (dgram.size()) {
  7. emit datagramReceived(peerInfo, clientMessage, dgram);
  8. connection->writeDatagramEncrypted(&serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1());
  9. } else if (connection->dtlsError() == QDtlsError::NoError) {
  10. emit warningMessage(peerInfo + ": " + tr("0 byte dgram, could be a re-connect attempt?"));
  11. } else {
  12. emit errorMessage(peerInfo + ": " + connection->dtlsErrorString());
  13. }
  14. }

服务端关闭DTLS服务调用QDtls::shutdown()

  1. void DtlsServer::shutdown()
  2. {
  3. for (const auto &connection : qExchange(knownClients, {}))
  4. connection->shutdown(&serverSocket);
  5. serverSocket.close();
  6. }

下面是出现问题或警告时的代码:

  1. const QString colorizer(QStringLiteral("<font color=\"%1\">%2</font><br>"));
  2. void MainWindow::addErrorMessage(const QString &message)
  3. {
  4. ui->serverInfo->insertHtml(colorizer.arg(QStringLiteral("Crimson"), message));
  5. }
  6. void MainWindow::addWarningMessage(const QString &message)
  7. {
  8. ui->serverInfo->insertHtml(colorizer.arg(QStringLiteral("DarkOrange"), message));
  9. }
  10. void MainWindow::addInfoMessage(const QString &message)
  11. {
  12. ui->serverInfo->insertHtml(colorizer.arg(QStringLiteral("DarkBlue"), message));
  13. }
  14. void MainWindow::addClientMessage(const QString &peerInfo, const QByteArray &datagram,
  15. const QByteArray &plainText)
  16. {
  17. static const QString messageColor = QStringLiteral("DarkMagenta");
  18. static const QString formatter = QStringLiteral("<br>---------------"
  19. "<br>A message from %1"
  20. "<br>DTLS datagram:<br> %2"
  21. "<br>As plain text:<br> %3");
  22. const QString html = formatter.arg(peerInfo, QString::fromUtf8(datagram.toHex(' ')),
  23. QString::fromUtf8(plainText));
  24. ui->messages->insertHtml(colorizer.arg(messageColor, html));
  25. }

发表评论

表情:
评论列表 (有 0 条评论,279人围观)

还没有评论,来说两句吧...

相关阅读