cutelyst 5.0.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
tcpserverbalancer.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2017-2018 Daniel Nicoletti <dantti12@gmail.com>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5#include "tcpserverbalancer.h"
6
7#include "server.h"
8#include "serverengine.h"
9#include "tcpserver.h"
10#include "tcpsslserver.h"
11
12#include <iostream>
13
14#include <QFile>
15#include <QLoggingCategory>
16#include <QSslKey>
17
18#ifdef Q_OS_LINUX
19# include <arpa/inet.h>
20# include <fcntl.h>
21# include <sys/socket.h>
22# include <sys/types.h>
23#endif
24
25Q_LOGGING_CATEGORY(C_SERVER_BALANCER, "cutelyst.server.tcpbalancer", QtWarningMsg)
26
27using namespace Cutelyst;
28
29#ifdef Q_OS_LINUX
30namespace {
31int listenReuse(const QHostAddress &address,
32 int listenQueue,
33 quint16 port,
34 bool reusePort,
35 bool startListening);
36}
37#endif
38
39TcpServerBalancer::TcpServerBalancer(Server *server)
40 : QTcpServer(server)
41 , m_server(server)
42{
43}
44
45TcpServerBalancer::~TcpServerBalancer()
46{
47#ifndef QT_NO_SSL
48 delete m_sslConfiguration;
49#endif // QT_NO_SSL
50}
51
52bool TcpServerBalancer::listen(const QString &line, Protocol *protocol, bool secure)
53{
54 m_protocol = protocol;
55
56 int commaPos = line.indexOf(QLatin1Char(','));
57 const QString addressPortString = line.mid(0, commaPos);
58
59 QString addressString;
60 int closeBracketPos = addressPortString.indexOf(QLatin1Char(']'));
61 if (closeBracketPos != -1) {
62 if (!line.startsWith(QLatin1Char('['))) {
63 std::cerr << "Failed to parse address: " << qPrintable(addressPortString) << '\n';
64 return false;
65 }
66 addressString = addressPortString.mid(1, closeBracketPos - 1);
67 } else {
68 addressString = addressPortString.section(QLatin1Char(':'), 0, -2);
69 }
70 const QString portString = addressPortString.section(QLatin1Char(':'), -1);
71
72 QHostAddress address;
73 if (addressString.isEmpty()) {
75 } else {
76 address.setAddress(addressString);
77 }
78
79 bool ok;
80 quint16 port = portString.toUInt(&ok);
81 if (!ok || (port < 1 || port > 35554)) {
82 port = 80;
83 }
84
85#ifndef QT_NO_SSL
86 if (secure) {
87 if (commaPos == -1) {
88 std::cerr << "No SSL certificate specified" << '\n';
89 return false;
90 }
91
92 const QString sslString = line.mid(commaPos + 1);
93 const QString certPath = sslString.section(QLatin1Char(','), 0, 0);
94 QFile certFile(certPath);
95 if (!certFile.open(QFile::ReadOnly)) {
96 std::cerr << "Failed to open SSL certificate" << qPrintable(certPath)
97 << qPrintable(certFile.errorString()) << '\n';
98 return false;
99 }
100 QSslCertificate cert(&certFile);
101 if (cert.isNull()) {
102 std::cerr << "Failed to parse SSL certificate" << '\n';
103 return false;
104 }
105
106 const QString keyPath = sslString.section(QLatin1Char(','), 1, 1);
107 QFile keyFile(keyPath);
108 if (!keyFile.open(QFile::ReadOnly)) {
109 std::cerr << "Failed to open SSL private key" << qPrintable(keyPath)
110 << qPrintable(keyFile.errorString()) << '\n';
111 return false;
112 }
113
114 QSsl::KeyAlgorithm algorithm = QSsl::Rsa;
115 const QString keyAlgorithm = sslString.section(QLatin1Char(','), 2, 2);
116 if (!keyAlgorithm.isEmpty()) {
117 if (keyAlgorithm.compare(QLatin1String("rsa"), Qt::CaseInsensitive) == 0) {
118 algorithm = QSsl::Rsa;
119 } else if (keyAlgorithm.compare(QLatin1String("ec"), Qt::CaseInsensitive) == 0) {
120 algorithm = QSsl::Ec;
121 } else {
122 std::cerr << "Failed to select SSL Key Algorithm" << qPrintable(keyAlgorithm)
123 << '\n';
124 return false;
125 }
126 }
127
128 QSslKey key(&keyFile, algorithm);
129 if (key.isNull()) {
130 std::cerr << "Failed to parse SSL private key" << '\n';
131 return false;
132 }
133
134 m_sslConfiguration = new QSslConfiguration;
135 m_sslConfiguration->setLocalCertificate(cert);
136 m_sslConfiguration->setPrivateKey(key);
137 m_sslConfiguration->setPeerVerifyMode(
138 QSslSocket::VerifyNone); // prevent asking for client certificate
139 if (m_server->httpsH2()) {
140 m_sslConfiguration->setAllowedNextProtocols(
141 {QByteArrayLiteral("h2"), QSslConfiguration::NextProtocolHttp1_1});
142 }
143 }
144#endif // QT_NO_SSL
145
146 m_address = address;
147 m_port = port;
148
149#ifdef Q_OS_LINUX
150 int socket = listenReuse(
151 address, m_server->listenQueue(), port, m_server->reusePort(), !m_server->reusePort());
152 if (socket > 0 && setSocketDescriptor(socket)) {
154 } else {
155 std::cerr << "Failed to listen on TCP: " << qPrintable(line) << " : "
156 << qPrintable(errorString()) << '\n';
157 return false;
158 }
159#else
160 setListenBacklogSize(m_server->listenQueue());
161 bool ret = QTcpServer::listen(address, port);
162 if (ret) {
164 } else {
165 std::cerr << "Failed to listen on TCP: " << qPrintable(line) << " : "
166 << qPrintable(errorString()) << '\n';
167 return false;
168 }
169#endif
170
171 m_serverName = serverAddress().toString().toLatin1() + ':' + QByteArray::number(port);
172 return true;
173}
174
175namespace {
176#ifdef Q_OS_LINUX
177// UnixWare 7 redefines socket -> _socket
178inline int qt_safe_socket(int domain, int type, int protocol, int flags = 0)
179{
180 Q_ASSERT((flags & ~O_NONBLOCK) == 0);
181
182 int fd;
183# ifdef QT_THREADSAFE_CLOEXEC
184 int newtype = type | SOCK_CLOEXEC;
185 if (flags & O_NONBLOCK) {
186 newtype |= SOCK_NONBLOCK;
187 }
188 fd = ::socket(domain, newtype, protocol);
189 return fd;
190# else
191 fd = ::socket(domain, type, protocol);
192 if (fd == -1) {
193 return -1;
194 }
195
196 ::fcntl(fd, F_SETFD, FD_CLOEXEC);
197
198 // set non-block too?
199 if (flags & O_NONBLOCK) {
200 ::fcntl(fd, F_SETFL, ::fcntl(fd, F_GETFL) | O_NONBLOCK);
201 }
202
203 return fd;
204# endif
205}
206
207int createNewSocket(QAbstractSocket::NetworkLayerProtocol &socketProtocol)
208{
209 int protocol = 0;
210
211 int domain = (socketProtocol == QAbstractSocket::IPv6Protocol ||
212 socketProtocol == QAbstractSocket::AnyIPProtocol)
213 ? AF_INET6
214 : AF_INET;
215 int type = SOCK_STREAM;
216
217 int socket = qt_safe_socket(domain, type, protocol, O_NONBLOCK);
218 if (socket < 0 && socketProtocol == QAbstractSocket::AnyIPProtocol && errno == EAFNOSUPPORT) {
219 domain = AF_INET;
220 socket = qt_safe_socket(domain, type, protocol, O_NONBLOCK);
221 socketProtocol = QAbstractSocket::IPv4Protocol;
222 }
223
224 if (socket < 0) {
225 int ecopy = errno;
226 switch (ecopy) {
227 case EPROTONOSUPPORT:
228 case EAFNOSUPPORT:
229 case EINVAL:
230 qCDebug(C_SERVER_BALANCER)
231 << "setError(QAbstractSocket::UnsupportedSocketOperationError, "
232 "ProtocolUnsupportedErrorString)";
233 break;
234 case ENFILE:
235 case EMFILE:
236 case ENOBUFS:
237 case ENOMEM:
238 qCDebug(C_SERVER_BALANCER)
239 << "setError(QAbstractSocket::SocketResourceError, ResourceErrorString)";
240 break;
241 case EACCES:
242 qCDebug(C_SERVER_BALANCER)
243 << "setError(QAbstractSocket::SocketAccessError, AccessErrorString)";
244 break;
245 default:
246 break;
247 }
248
249# if defined(QNATIVESOCKETENGINE_DEBUG)
250 qCDebug(C_SERVER_BALANCER,
251 "QNativeSocketEnginePrivate::createNewSocket(%d, %d) == false (%s)",
252 socketType,
253 socketProtocol,
254 strerror(ecopy));
255# endif
256
257 return false;
258 }
259
260# if defined(QNATIVESOCKETENGINE_DEBUG)
261 qCDebug(C_SERVER_BALANCER,
262 "QNativeSocketEnginePrivate::createNewSocket(%d, %d) == true",
263 socketType,
264 socketProtocol);
265# endif
266
267 return socket;
268}
269
270union qt_sockaddr {
271 sockaddr a;
272 sockaddr_in a4;
273 sockaddr_in6 a6;
274};
275
276# define QT_SOCKLEN_T int
277# define QT_SOCKET_BIND ::bind
278
279namespace SetSALen {
280template <typename T>
281void set(T *sa, typename std::enable_if<(&T::sa_len, true), QT_SOCKLEN_T>::type len)
282{
283 sa->sa_len = len;
284}
285template <typename T>
286void set(T *sin6, typename std::enable_if<(&T::sin6_len, true), QT_SOCKLEN_T>::type len)
287{
288 sin6->sin6_len = len;
289}
290template <typename T>
291void set(T *, ...)
292{
293}
294} // namespace SetSALen
295
296void setPortAndAddress(quint16 port,
297 const QHostAddress &address,
299 qt_sockaddr *aa,
300 int *sockAddrSize)
301{
302 if (address.protocol() == QAbstractSocket::IPv6Protocol ||
304 socketProtocol == QAbstractSocket::IPv6Protocol ||
305 socketProtocol == QAbstractSocket::AnyIPProtocol) {
306 memset(&aa->a6, 0, sizeof(sockaddr_in6));
307 aa->a6.sin6_family = AF_INET6;
308 // #if QT_CONFIG(networkinterface)
309 // aa->a6.sin6_scope_id = scopeIdFromString(address.scopeId());
310 // #endif
311 aa->a6.sin6_port = htons(port);
312 Q_IPV6ADDR tmp = address.toIPv6Address();
313 memcpy(&aa->a6.sin6_addr, &tmp, sizeof(tmp));
314 *sockAddrSize = sizeof(sockaddr_in6);
315 SetSALen::set(&aa->a, sizeof(sockaddr_in6));
316 } else {
317 memset(&aa->a, 0, sizeof(sockaddr_in));
318 aa->a4.sin_family = AF_INET;
319 aa->a4.sin_port = htons(port);
320 aa->a4.sin_addr.s_addr = htonl(address.toIPv4Address());
321 *sockAddrSize = sizeof(sockaddr_in);
322 SetSALen::set(&aa->a, sizeof(sockaddr_in));
323 }
324}
325
326bool nativeBind(int socketDescriptor, const QHostAddress &address, quint16 port)
327{
328 qt_sockaddr aa;
329 int sockAddrSize;
330 setPortAndAddress(port, address, address.protocol(), &aa, &sockAddrSize);
331
332# ifdef IPV6_V6ONLY
333 if (aa.a.sa_family == AF_INET6) {
334 int ipv6only = 0;
335 if (address.protocol() == QAbstractSocket::IPv6Protocol) {
336 ipv6only = 1;
337 }
338 // default value of this socket option varies depending on unix variant (or system
339 // configuration on BSD), so always set it explicitly
340 ::setsockopt(
341 socketDescriptor, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &ipv6only, sizeof(ipv6only));
342 }
343# endif
344
345 int bindResult = ::bind(socketDescriptor, &aa.a, sockAddrSize);
346 if (bindResult < 0 && errno == EAFNOSUPPORT &&
348 // retry with v4
349 aa.a4.sin_family = AF_INET;
350 aa.a4.sin_port = htons(port);
351 aa.a4.sin_addr.s_addr = htonl(address.toIPv4Address());
352 sockAddrSize = sizeof(aa.a4);
353 bindResult = QT_SOCKET_BIND(socketDescriptor, &aa.a, sockAddrSize);
354 }
355
356 if (bindResult < 0) {
357# if defined(QNATIVESOCKETENGINE_DEBUG)
358 int ecopy = errno;
359# endif
360 // switch(errno) {
361 // case EADDRINUSE:
362 // setError(QAbstractSocket::AddressInUseError, AddressInuseErrorString);
363 // break;
364 // case EACCES:
365 // setError(QAbstractSocket::SocketAccessError, AddressProtectedErrorString);
366 // break;
367 // case EINVAL:
368 // setError(QAbstractSocket::UnsupportedSocketOperationError,
369 // OperationUnsupportedErrorString); break;
370 // case EADDRNOTAVAIL:
371 // setError(QAbstractSocket::SocketAddressNotAvailableError,
372 // AddressNotAvailableErrorString); break;
373 // default:
374 // break;
375 // }
376
377# if defined(QNATIVESOCKETENGINE_DEBUG)
378 qCDebug(C_SERVER_BALANCER,
379 "QNativeSocketEnginePrivate::nativeBind(%s, %i) == false (%s)",
380 address.toString().toLatin1().constData(),
381 port,
382 strerror(ecopy));
383# endif
384
385 return false;
386 }
387
388# if defined(QNATIVESOCKETENGINE_DEBUG)
389 qCDebug(C_SERVER_BALANCER,
390 "QNativeSocketEnginePrivate::nativeBind(%s, %i) == true",
391 address.toString().toLatin1().constData(),
392 port);
393# endif
394 // socketState = QAbstractSocket::BoundState;
395 return true;
396}
397
398int listenReuse(const QHostAddress &address,
399 int listenQueue,
400 quint16 port,
401 bool reusePort,
402 bool startListening)
403{
405
406 int socket = createNewSocket(proto);
407 if (socket < 0) {
408 qCCritical(C_SERVER_BALANCER) << "Failed to create new socket";
409 return -1;
410 }
411
412 int optval = 1;
413 // SO_REUSEADDR is set by default on QTcpServer and allows to bind again
414 // without having to wait all previous connections to close
415 if (::setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))) {
416 qCCritical(C_SERVER_BALANCER) << "Failed to set SO_REUSEADDR on socket" << socket;
417 return -1;
418 }
419
420 if (reusePort) {
421 if (::setsockopt(socket, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval))) {
422 qCCritical(C_SERVER_BALANCER) << "Failed to set SO_REUSEPORT on socket" << socket;
423 return -1;
424 }
425 }
426
427 if (!nativeBind(socket, address, port)) {
428 qCCritical(C_SERVER_BALANCER) << "Failed to bind to socket" << socket;
429 return -1;
430 }
431
432 if (startListening && ::listen(socket, listenQueue) < 0) {
433 qCCritical(C_SERVER_BALANCER) << "Failed to listen to socket" << socket;
434 return -1;
435 }
436
437 return socket;
438}
439#endif // Q_OS_LINUX
440} // namespace
441
442void TcpServerBalancer::setBalancer(bool enable)
443{
444 m_balancer = enable;
445}
446
447void TcpServerBalancer::incomingConnection(qintptr handle)
448{
449 TcpServer *serverIdle = m_servers.at(m_currentServer++ % m_servers.size());
450
451 Q_EMIT serverIdle->createConnection(handle);
452}
453
454TcpServer *TcpServerBalancer::createServer(ServerEngine *engine)
455{
456 TcpServer *server;
457 if (m_sslConfiguration) {
458#ifndef QT_NO_SSL
459 auto sslServer = new TcpSslServer(m_serverName, m_protocol, m_server, engine);
460 sslServer->setSslConfiguration(*m_sslConfiguration);
461 server = sslServer;
462#endif // QT_NO_SSL
463 } else {
464 server = new TcpServer(m_serverName, m_protocol, m_server, engine);
465 }
466 connect(engine, &ServerEngine::shutdown, server, &TcpServer::shutdown);
467
468 if (m_balancer) {
469 connect(engine, &ServerEngine::started, this, [this, server]() {
470 m_servers.push_back(server);
473 connect(server,
474 &TcpServer::createConnection,
475 server,
476 &TcpServer::incomingConnection,
478 } else {
479
480#ifdef Q_OS_LINUX
481 if (m_server->reusePort()) {
482 connect(engine, &ServerEngine::started, this, [this, server]() {
483 int socket = listenReuse(
484 m_address, m_server->listenQueue(), m_port, m_server->reusePort(), true);
485 if (!server->setSocketDescriptor(socket)) {
486 qFatal("Failed to set server socket descriptor, reuse-port");
487 }
489 return server;
490 }
491#endif
492
493 if (server->setSocketDescriptor(socketDescriptor())) {
494 server->pauseAccepting();
495 connect(engine,
496 &ServerEngine::started,
497 server,
500 } else {
501 qFatal("Failed to set server socket descriptor");
502 }
503 }
504
505 return server;
506}
507
508#include "moc_tcpserverbalancer.cpp"
Implements a web server.
Definition server.h:60
The Cutelyst namespace holds all public Cutelyst API.
const char * constData() const const
QByteArray number(double n, char format, int precision)
int protocol() const const
bool setAddress(const QString &address)
quint32 toIPv4Address(bool *ok) const const
Q_IPV6ADDR toIPv6Address() const const
QString toString() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
KeyAlgorithm
void setAllowedNextProtocols(const QList< QByteArray > &protocols)
void setLocalCertificate(const QSslCertificate &certificate)
void setPeerVerifyMode(QSslSocket::PeerVerifyMode mode)
void setPrivateKey(const QSslKey &key)
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) const const
QString section(QChar sep, qsizetype start, qsizetype end, QString::SectionFlags flags) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
uint toUInt(bool *ok, int base) const const
CaseInsensitive
QueuedConnection
QString errorString() const const
bool listen(const QHostAddress &address, quint16 port)
void pauseAccepting()
void resumeAccepting()
QHostAddress serverAddress() const const
void setListenBacklogSize(int size)
bool setSocketDescriptor(qintptr socketDescriptor)
qintptr socketDescriptor() const const