警告
本节包含从C++自动翻译成Python的代码片段,可能存在错误。
DTLS服务器#
本示例演示了如何实现一个简单的DTLS服务器。
注意
DTLS服务器示例旨在与DTLS客户端示例同时运行。
服务器由DtlsServer类实现。它使用QUdpSocket
、QDtlsClientVerifier
和QDtls
来测试每个客户端的可达性,完成握手,并读取和写入加密的消息。
class DtlsServer(QObject): Q_OBJECT # public DtlsServer() ~DtlsServer() listen = bool(QHostAddress address, quint16 port) isListening = bool() def close(): # signals def errorMessage(message): def warningMessage(message): def infoMessage(message): def datagramReceived(peerInfo, cipherText,): plainText) = QByteArray() # private slots def readyRead(): def pskRequired(auth): # private def handleNewConnection(peerAddress, peerPort,): clientHello) = QByteArray() def doHandshake(newConnection, clientHello): def decryptDatagram(connection, clientMessage): def shutdown(): listening = False serverSocket = QUdpSocket() serverConfiguration = QSslConfiguration() cookieSender = QDtlsClientVerifier() std.vector<std.unique_ptr<QDtls>> knownClients Q_DISABLE_COPY(DtlsServer)
构造函数连接了QUdpSocket::readyRead()信号到其readyRead()槽,并设置了所需的TLS配置。
def __init__(self): serverSocket.readyRead.connect(self.readyRead) serverConfiguration = QSslConfiguration.defaultDtlsConfiguration() serverConfiguration.setPreSharedKeyIdentityHint("Qt DTLS example server") serverConfiguration.setPeerVerifyMode(QSslSocket.VerifyNone)
注意
服务器没有使用证书,依赖于预共享密钥(PSK)握手。
listen()绑定QUdpSocket
def listen(self, QHostAddress address, quint16 port): if address != serverSocket.localAddress() or port != serverSocket.localPort(): shutdown() listening = serverSocket.bind(address, port) if not listening: errorMessage.emit(serverSocket.errorString()) else: listening = True return listening
readyRead()槽处理传入的数据报
... bytesToRead = serverSocket.pendingDatagramSize() if bytesToRead <= 0: warningMessage.emit(tr("A spurious read notification")) return dgram = QByteArray(bytesToRead, Qt.Uninitialized) peerAddress = QHostAddress() peerPort = 0 bytesRead = serverSocket.readDatagram(dgram.data(), dgram.size(), peerAddress, peerPort) if bytesRead <= 0: warningMessage.emit(tr("Failed to read a datagram: ") + serverSocket.errorString()) return dgram.resize(bytesRead) ...
在提取地址和端口号后,服务器首先检查该数据报是否来自已知的对等端
... if peerAddress.isNull() or not peerPort: warningMessage.emit(tr("Failed to extract peer info (address, port)")) return client = std::find_if(knownClients.begin(), knownClients.end(), [](std.unique_ptr<QDtls> connection){ return connection.peerAddress() == peerAddress and 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
def handleNewConnection(self, peerAddress,): quint16 peerPort, QByteArray clientHello) if not listening: return peerInfo = peer_info(peerAddress, peerPort) if cookieSender.verifyClient(serverSocket, clientHello, peerAddress, peerPort): infoMessage.emit(peerInfo + tr(": verified, starting a handshake")) ...
如果验证新客户端为可达的DTLS客户端,服务器创建并配置一个新的 QDtls
对象,并开始服务器端的握手
... std.unique_ptr<QDtls> newConnection{QDtls(){QSslSocket.SslServerMode}} newConnection.setDtlsConfiguration(serverConfiguration) newConnection.setPeer(peerAddress, peerPort) newConnection.connect(newConnection.get(), QDtls.pskRequired, self.pskRequired) knownClients.push_back(std.move(newConnection)) doHandshake(knownClients.back().get(), clientHello) ...
doHandshake()进度通过握手阶段
def doHandshake(self, newConnection, clientHello): result = newConnection.doHandshake(serverSocket, clientHello) if not result: errorMessage.emit(newConnection.dtlsErrorString()) return peerInfo = peer_info(newConnection.peerAddress(), newConnection.peerPort()) switch (newConnection.handshakeState()) { elif graphicsApi == QDtls.HandshakeInProgress: infoMessage.emit(peerInfo + tr(": handshake is in progress ...")) break elif graphicsApi == QDtls.HandshakeComplete: infoMessage.emit(tr("Connection with %1 encrypted. %2") .arg(peerInfo, connection_info(newConnection))) break else: Q_UNREACHABLE()
在握手阶段,会发出pskRequired()
信号,而pskRequired()槽提供了预共享密钥
def pskRequired(self, auth): Q_ASSERT(auth) infoMessage.emit(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()解密由对等端发送的后续数据报。服务器也会向对等端发送加密响应。
def decryptDatagram(self, connection, clientMessage): Q_ASSERT(connection.isConnectionEncrypted()) peerInfo = peer_info(connection.peerAddress(), connection.peerPort()) dgram = connection.decryptDatagram(serverSocket, clientMessage) if dgram.size(): datagramReceived.emit(peerInfo, clientMessage, dgram) connection.writeDatagramEncrypted(serverSocket, tr("to %1: ACK").arg(peerInfo).toLatin1()) elif connection.dtlsError() == QDtlsError.NoError: warningMessage.emit(peerInfo + ": " + tr("0 byte dgram, could be a re-connect attempt?")) else: errorMessage.emit(peerInfo + ": " + connection.dtlsErrorString())
服务器通过调用 shutdown()
来关闭其DTLS连接
def shutdown(self): for connection in std::exchange(knownClients, {}): connection.shutdown(serverSocket) serverSocket.close()
在其运行过程中,服务器通过发射errorMessage()、warningMessage()、infoMessage()和datagramReceived()信号来报告错误、信息消息和解密的数据报。这些消息由服务器界面进行记录
colorizer = QString(QStringLiteral("<font color=\"%1\">%2")) def addErrorMessage(self, message): ui.serverInfo.insertHtml(colorizer.arg("Crimson", message)) def addWarningMessage(self, message): ui.serverInfo.insertHtml(colorizer.arg("DarkOrange", message)) def addInfoMessage(self, message): ui.serverInfo.insertHtml(colorizer.arg("DarkBlue", message)) def addClientMessage(self, peerInfo, datagram,): QByteArray plainText) messageColor = "DarkMagenta" formatter = QStringLiteral("<br>---------------"() "<br>A message from %1" "<br>DTLS datagram:<br> %2" "<br>As plain text:<br> %3") html = formatter.arg(peerInfo, QString.fromUtf8(datagram.toHex(' ')), QString.fromUtf8(plainText)) ui.messages.insertHtml(colorizer.arg(messageColor, html))