1 | // Copyright (C) 2017 Witekio. |
2 | // Copyright (C) 2018 The Qt Company Ltd. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
4 | |
5 | #include "qcoapqudpconnection_p.h" |
6 | |
7 | #include <QtCore/qloggingcategory.h> |
8 | #include <QtNetwork/qnetworkdatagram.h> |
9 | |
10 | #if QT_CONFIG(dtls) |
11 | #include <QtNetwork/QDtls> |
12 | #include <QtNetwork/QSslPreSharedKeyAuthenticator> |
13 | #include <QtNetwork/QSslConfiguration> |
14 | #include <QtNetwork/QSslKey> |
15 | #endif |
16 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | Q_DECLARE_LOGGING_CATEGORY(lcCoapConnection) |
20 | |
21 | /*! |
22 | \internal |
23 | |
24 | \class QCoapQUdpConnection |
25 | \inmodule QtCoap |
26 | |
27 | \brief The QCoapQUdpConnection class handles the transfer of frames to |
28 | and from a server. |
29 | |
30 | \reentrant |
31 | |
32 | The QCoapQUdpConnection class is used by the QCoapClient class to send |
33 | requests to a server. It has a socket listening for UDP messages, |
34 | that is used to send the CoAP frames. |
35 | |
36 | When a reply is available, the QCoapQUdpConnection object emits a readyRead() |
37 | signal. |
38 | |
39 | \sa QCoapClient |
40 | */ |
41 | |
42 | /*! |
43 | Constructs a new QCoapQUdpConnection for the given \a securityMode and |
44 | sets \a parent as the parent object. |
45 | |
46 | \note Since QtCoap::RawPublicKey is not supported yet, the connection |
47 | will fall back to the QtCoap::NoSecurity in the QtCoap::RawPublicKey mode. |
48 | That is, the connection won't be secure in this mode. |
49 | */ |
50 | QCoapQUdpConnection::QCoapQUdpConnection(QtCoap::SecurityMode securityMode, QObject *parent) : |
51 | QCoapQUdpConnection(*new QCoapQUdpConnectionPrivate(securityMode), parent) |
52 | { |
53 | } |
54 | |
55 | /*! |
56 | \internal |
57 | |
58 | Constructs a new QCoapQUdpConnection as a child of \a parent, with \a dd as |
59 | its \c d_ptr. This constructor must be used when internally subclassing |
60 | the QCoapQUdpConnection class. |
61 | */ |
62 | QCoapQUdpConnection::QCoapQUdpConnection(QCoapQUdpConnectionPrivate &dd, QObject *parent) : |
63 | QCoapConnection(dd, parent) |
64 | { |
65 | Q_D(QCoapQUdpConnection); |
66 | |
67 | createSocket(); |
68 | |
69 | if (isSecure()) { |
70 | #if QT_CONFIG(dtls) |
71 | connect(sender: this, signal: &QCoapConnection::securityConfigurationChanged, |
72 | slot: [this]() { |
73 | Q_D(QCoapQUdpConnection); |
74 | d->setSecurityConfiguration(securityConfiguration()); |
75 | }); |
76 | |
77 | auto configuration = QSslConfiguration::defaultDtlsConfiguration(); |
78 | |
79 | switch (d->securityMode) { |
80 | case QtCoap::SecurityMode::RawPublicKey: |
81 | qCWarning(lcCoapConnection, "RawPublicKey security is not supported yet," |
82 | "disabling security" ); |
83 | d->securityMode = QtCoap::SecurityMode::NoSecurity; |
84 | break; |
85 | case QtCoap::SecurityMode::PreSharedKey: |
86 | d->dtls = new QDtls(QSslSocket::SslClientMode, this); |
87 | configuration.setPeerVerifyMode(QSslSocket::VerifyNone); |
88 | d->dtls->setDtlsConfiguration(configuration); |
89 | |
90 | connect(sender: d->dtls.data(), signal: &QDtls::pskRequired, context: this, slot: &QCoapQUdpConnection::pskRequired); |
91 | connect(sender: d->dtls.data(), signal: &QDtls::handshakeTimeout, |
92 | context: this, slot: &QCoapQUdpConnection::handshakeTimeout); |
93 | break; |
94 | case QtCoap::SecurityMode::Certificate: |
95 | d->dtls = new QDtls(QSslSocket::SslClientMode, this); |
96 | configuration.setPeerVerifyMode(QSslSocket::VerifyPeer); |
97 | d->dtls->setDtlsConfiguration(configuration); |
98 | |
99 | connect(sender: d->dtls.data(), signal: &QDtls::handshakeTimeout, |
100 | context: this, slot: &QCoapQUdpConnection::handshakeTimeout); |
101 | break; |
102 | default: |
103 | break; |
104 | } |
105 | #else |
106 | qCWarning(lcCoapConnection, "DTLS is disabled, falling back to QtCoap::NoSecurity mode." ); |
107 | d->securityMode = QtCoap::SecurityMode::NoSecurity; |
108 | #endif |
109 | } |
110 | } |
111 | |
112 | /*! |
113 | \internal |
114 | |
115 | Creates the socket used by the connection and sets it in connection's private |
116 | class. |
117 | */ |
118 | void QCoapQUdpConnection::createSocket() |
119 | { |
120 | Q_D(QCoapQUdpConnection); |
121 | |
122 | d->udpSocket = new QUdpSocket(this); |
123 | |
124 | connect(sender: d->udpSocket.data(), signal: &QUdpSocket::readyRead, slot: [this]() { |
125 | Q_D(QCoapQUdpConnection); |
126 | d->socketReadyRead(); |
127 | }); |
128 | connect(sender: d->udpSocket.data(), signal: &QUdpSocket::errorOccurred, |
129 | slot: [this](QAbstractSocket::SocketError socketError) { |
130 | qCWarning(lcCoapConnection) << "CoAP UDP socket error" << socketError |
131 | << socket()->errorString(); |
132 | emit error(error: socketError); |
133 | }); |
134 | } |
135 | |
136 | QCoapQUdpConnectionPrivate::QCoapQUdpConnectionPrivate(QtCoap::SecurityMode security) |
137 | : QCoapConnectionPrivate(security) |
138 | #if QT_CONFIG(dtls) |
139 | , dtls(nullptr) |
140 | #endif |
141 | , udpSocket(nullptr) |
142 | { |
143 | } |
144 | |
145 | QCoapQUdpConnectionPrivate::~QCoapQUdpConnectionPrivate() |
146 | { |
147 | #if QT_CONFIG(dtls) |
148 | if (dtls && dtls->isConnectionEncrypted()) { |
149 | Q_ASSERT(udpSocket); |
150 | dtls->shutdown(socket: udpSocket); |
151 | } |
152 | #endif |
153 | } |
154 | |
155 | /*! |
156 | \internal |
157 | |
158 | Binds the socket to a random port and returns \c true if it succeeds. |
159 | */ |
160 | bool QCoapQUdpConnectionPrivate::bind() |
161 | { |
162 | return socket()->bind(addr: QHostAddress::Any, port: 0, mode: QAbstractSocket::ShareAddress); |
163 | } |
164 | |
165 | /*! |
166 | \internal |
167 | |
168 | Binds the socket if it is not already done and emits the bound() signal when |
169 | the socket is ready. |
170 | */ |
171 | void QCoapQUdpConnectionPrivate::bindSocket() |
172 | { |
173 | Q_Q(QCoapQUdpConnection); |
174 | |
175 | if (state != QCoapQUdpConnection::ConnectionState::Bound && bind()) |
176 | emit q->bound(); |
177 | } |
178 | |
179 | /*! |
180 | \internal |
181 | |
182 | Prepares the socket for data transmission to the given \a host and |
183 | \a port by binding the socket. In case of a secure connection also |
184 | starts the handshake with the server. |
185 | Emits the bound() signal when the transport is ready. |
186 | */ |
187 | void QCoapQUdpConnection::bind(const QString &host, quint16 port) |
188 | { |
189 | Q_D(QCoapQUdpConnection); |
190 | |
191 | if (!isSecure()) { |
192 | d->bindSocket(); |
193 | #if QT_CONFIG(dtls) |
194 | } else { |
195 | Q_ASSERT(d->dtls); |
196 | if (d->dtls->isConnectionEncrypted()) { |
197 | emit bound(); |
198 | } else if (socket()->state() == QAbstractSocket::UnconnectedState) { |
199 | socket()->bind(); |
200 | d->dtls->setPeer(address: QHostAddress(host), port); |
201 | if (!d->dtls->doHandshake(socket: d->socket())) |
202 | qCWarning(lcCoapConnection) << "Handshake error: " << d->dtls->dtlsErrorString(); |
203 | } |
204 | #else |
205 | Q_UNUSED(host); |
206 | Q_UNUSED(port); |
207 | #endif |
208 | } |
209 | } |
210 | |
211 | /*! |
212 | \internal |
213 | |
214 | Sends the given \a data frame to the \a host at the \a port. |
215 | */ |
216 | void QCoapQUdpConnection::writeData(const QByteArray &data, const QString &host, quint16 port) |
217 | { |
218 | Q_D(QCoapQUdpConnection); |
219 | d->writeToSocket(data, host, port); |
220 | } |
221 | |
222 | /*! |
223 | \internal |
224 | |
225 | \brief Close the UDP socket |
226 | |
227 | In the case of a secure connection, this also interrupts any ongoing |
228 | hand-shake and shuts down the DTLS connection. |
229 | */ |
230 | void QCoapQUdpConnection::close() |
231 | { |
232 | Q_D(QCoapQUdpConnection); |
233 | |
234 | #if QT_CONFIG(dtls) |
235 | if (isSecure()) { |
236 | if (d->dtls->handshakeState() == QDtls::HandshakeInProgress) |
237 | d->dtls->abortHandshake(socket: d->socket()); |
238 | |
239 | if (d->dtls->isConnectionEncrypted()) |
240 | d->dtls->shutdown(socket: d->socket()); |
241 | } |
242 | #endif |
243 | d->socket()->close(); |
244 | } |
245 | |
246 | /*! |
247 | \internal |
248 | |
249 | Sets the QUdpSocket socket \a option to \a value. |
250 | */ |
251 | void QCoapQUdpConnection::setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value) |
252 | { |
253 | Q_D(QCoapQUdpConnection); |
254 | d->socket()->setSocketOption(option, value); |
255 | } |
256 | |
257 | /*! |
258 | \internal |
259 | |
260 | Sends the given \a data frame to the \a host at the \a port. |
261 | */ |
262 | void QCoapQUdpConnectionPrivate::writeToSocket(const QByteArray &data, const QString &host, quint16 port) |
263 | { |
264 | #if QT_CONFIG(dtls) |
265 | Q_Q(QCoapQUdpConnection); |
266 | #endif |
267 | if (!socket()->isWritable()) { |
268 | bool opened = socket()->open(mode: socket()->openMode() | QIODevice::WriteOnly); |
269 | if (!opened) { |
270 | qCWarning(lcCoapConnection, "Failed to open the UDP socket with write permission" ); |
271 | return; |
272 | } |
273 | } |
274 | |
275 | QHostAddress hostAddress(host); |
276 | if (hostAddress.isNull()) { |
277 | qCWarning(lcCoapConnection) << "Invalid host IP address" << host |
278 | << "- only IPv4/IPv6 destination addresses are supported." ; |
279 | return; |
280 | } |
281 | |
282 | const qint64 bytesWritten = |
283 | #if QT_CONFIG(dtls) |
284 | q->isSecure() ? (Q_ASSERT(dtls), dtls->writeDatagramEncrypted(socket: socket(), dgram: data)) : |
285 | #endif |
286 | socket()->writeDatagram(datagram: data, host: hostAddress, port); |
287 | |
288 | if (bytesWritten < 0) |
289 | qCWarning(lcCoapConnection) << "Failed to write datagram:" << socket()->errorString(); |
290 | } |
291 | |
292 | /*! |
293 | \internal |
294 | |
295 | This slot reads all data stored in the socket and emits a readyRead() |
296 | signal for each received datagram. |
297 | */ |
298 | void QCoapQUdpConnectionPrivate::socketReadyRead() |
299 | { |
300 | Q_Q(QCoapQUdpConnection); |
301 | |
302 | if (!socket()->isReadable()) { |
303 | bool opened = socket()->open(mode: socket()->openMode() | QIODevice::ReadOnly); |
304 | if (!opened) { |
305 | qCWarning(lcCoapConnection, "Failed to open the UDP socket with read permission" ); |
306 | return; |
307 | } |
308 | } |
309 | |
310 | while (socket()->hasPendingDatagrams()) { |
311 | if (!q->isSecure()) { |
312 | const auto &datagram = socket()->receiveDatagram(); |
313 | emit q->readyRead(data: datagram.data(), sender: datagram.senderAddress()); |
314 | #if QT_CONFIG(dtls) |
315 | } else { |
316 | handleEncryptedDatagram(); |
317 | #endif |
318 | } |
319 | } |
320 | } |
321 | |
322 | /*! |
323 | \internal |
324 | |
325 | Returns the socket. |
326 | */ |
327 | QUdpSocket *QCoapQUdpConnection::socket() const |
328 | { |
329 | Q_D(const QCoapQUdpConnection); |
330 | return d->udpSocket; |
331 | } |
332 | |
333 | /*! |
334 | \internal |
335 | |
336 | Sets the DTLS configuration. |
337 | */ |
338 | void QCoapQUdpConnectionPrivate::setSecurityConfiguration( |
339 | const QCoapSecurityConfiguration &configuration) |
340 | { |
341 | #if QT_CONFIG(dtls) |
342 | Q_ASSERT(dtls); |
343 | auto dtlsConfig = dtls->dtlsConfiguration(); |
344 | |
345 | if (!configuration.defaultCipherString().isEmpty()) { |
346 | dtlsConfig.setBackendConfigurationOption(name: "CipherString" , |
347 | value: configuration.defaultCipherString()); |
348 | } |
349 | |
350 | if (!configuration.caCertificates().isEmpty()) |
351 | dtlsConfig.setCaCertificates(configuration.caCertificates().toList()); |
352 | |
353 | if (!configuration.localCertificateChain().isEmpty()) |
354 | dtlsConfig.setLocalCertificateChain(configuration.localCertificateChain().toList()); |
355 | |
356 | if (!configuration.privateKey().isNull()) { |
357 | if (configuration.privateKey().algorithm() != QSsl::Opaque) { |
358 | QSslKey privateKey(configuration.privateKey().key(), |
359 | configuration.privateKey().algorithm(), |
360 | configuration.privateKey().encodingFormat(), |
361 | QSsl::PrivateKey, |
362 | configuration.privateKey().passPhrase()); |
363 | dtlsConfig.setPrivateKey(privateKey); |
364 | } else if (configuration.privateKey().handle()) { |
365 | QSslKey opaqueKey(configuration.privateKey().handle()); |
366 | dtlsConfig.setPrivateKey(opaqueKey); |
367 | } else { |
368 | qCWarning(lcCoapConnection, "Failed to set private key, the provided key is invalid" ); |
369 | } |
370 | } |
371 | |
372 | dtls->setDtlsConfiguration(dtlsConfig); |
373 | #else |
374 | Q_UNUSED(configuration); |
375 | #endif |
376 | } |
377 | |
378 | #if QT_CONFIG(dtls) |
379 | /*! |
380 | \internal |
381 | |
382 | This slot is invoked when PSK authentication is required. It is used |
383 | for setting the identity and pre shared key in order for the TLS handshake |
384 | to complete. |
385 | */ |
386 | void QCoapQUdpConnection::pskRequired(QSslPreSharedKeyAuthenticator *authenticator) |
387 | { |
388 | Q_ASSERT(authenticator); |
389 | authenticator->setIdentity(securityConfiguration().preSharedKeyIdentity()); |
390 | authenticator->setPreSharedKey(securityConfiguration().preSharedKey()); |
391 | } |
392 | |
393 | /*! |
394 | \internal |
395 | |
396 | This slot handles handshake timeouts. |
397 | */ |
398 | void QCoapQUdpConnection::handshakeTimeout() |
399 | { |
400 | Q_D(QCoapQUdpConnection); |
401 | |
402 | qCWarning(lcCoapConnection, "Handshake timeout, trying to re-transmit" ); |
403 | if (d->dtls->handshakeState() == QDtls::HandshakeInProgress && |
404 | !d->dtls->handleTimeout(socket: d->udpSocket)) { |
405 | qCWarning(lcCoapConnection) << "Failed to re-transmit" << d->dtls->dtlsErrorString(); |
406 | } |
407 | } |
408 | |
409 | /*! |
410 | \internal |
411 | |
412 | Returns a decrypted datagram received from a UDP socket. |
413 | */ |
414 | QNetworkDatagram QCoapQUdpConnectionPrivate::receiveDatagramDecrypted() const |
415 | { |
416 | auto datagram = socket()->receiveDatagram(); |
417 | const QByteArray plainText = dtls->decryptDatagram(socket: socket(), dgram: datagram.data()); |
418 | datagram.setData(plainText); |
419 | return datagram; |
420 | } |
421 | |
422 | /*! |
423 | \internal |
424 | |
425 | If the connection is encrypted, emits the readyRead() signal for |
426 | the pending datagram. Otherwise starts the DTLS handshake and |
427 | if it is completed, starts to send the pending request. |
428 | */ |
429 | void QCoapQUdpConnectionPrivate::handleEncryptedDatagram() |
430 | { |
431 | Q_Q(QCoapQUdpConnection); |
432 | |
433 | Q_ASSERT(dtls); |
434 | |
435 | if (dtls->isConnectionEncrypted()) { |
436 | const auto &datagram = receiveDatagramDecrypted(); |
437 | emit q->readyRead(data: datagram.data(), sender: datagram.senderAddress()); |
438 | } else { |
439 | if (!dtls->doHandshake(socket: socket(), dgram: socket()->receiveDatagram().data())) { |
440 | qCWarning(lcCoapConnection) << "Handshake error: " << dtls->dtlsErrorString(); |
441 | return; |
442 | } |
443 | |
444 | // The handshake is successfully done, emit the bound() signal to inform |
445 | // listeners that the socket is ready to start transferring data. |
446 | if (dtls->isConnectionEncrypted()) |
447 | emit q->bound(); |
448 | } |
449 | } |
450 | |
451 | #endif // dtls |
452 | |
453 | QT_END_NAMESPACE |
454 | |