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