cutelyst 5.1.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#if defined(_WIN32)
6# ifndef _WIN32_WINNT
7# define _WIN32_WINNT 0x0601
8# endif
9# ifndef WIN32_LEAN_AND_MEAN
10# define WIN32_LEAN_AND_MEAN
11# endif
12# include <winsock2.h>
13# include <ws2tcpip.h>
14#endif
15
16#include "tcpserverbalancer.h"
17
18#include "server.h"
19#include "serverengine.h"
20#include "tcpserver.h"
21#include "tcpsslserver.h"
22
23#include <iostream>
24#include <mutex>
25
26#include <QFile>
27#include <QLoggingCategory>
28#include <QSslKey>
29
30#ifdef Q_OS_LINUX
31# include <arpa/inet.h>
32# include <fcntl.h>
33# include <sys/socket.h>
34# include <sys/types.h>
35# include <unistd.h>
36#endif
37
38Q_LOGGING_CATEGORY(C_SERVER_BALANCER, "cutelyst.server.tcpbalancer", QtWarningMsg)
39
40using namespace Cutelyst;
41
42#ifdef Q_OS_LINUX
43namespace {
44int listenReuse(const QHostAddress &address,
45 int listenQueue,
46 quint16 port,
47 bool reusePort,
48 bool startListening);
49}
50#endif
51
52#ifdef Q_OS_WIN
53namespace {
54bool ensureWinsockInitialized(QString *errorOut)
55{
56 static std::once_flag once;
57 static int wsaInitError = 0;
58 std::call_once(once, [] {
59 WSADATA wsaData;
60 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
61 wsaInitError = WSAGetLastError();
62 }
63 });
64 if (wsaInitError != 0) {
65 if (errorOut) {
66 *errorOut = QStringLiteral("WSAStartup failed (Windows socket error %1)")
67 .arg(wsaInitError);
68 }
69 return false;
70 }
71 return true;
72}
73
74QString windowsSocketErrorString(int error)
75{
76 switch (error) {
77 case WSAEADDRINUSE:
78 return QStringLiteral("The bound address is already in use");
79 case WSAEACCES:
80 return QStringLiteral("The requested address is a protected address and requires "
81 "appropriate privileges");
82 case WSAEADDRNOTAVAIL:
83 return QStringLiteral("The requested address is not valid in this context");
84 case WSANOTINITIALISED:
85 return QStringLiteral("Winsock has not been initialized");
86 default:
87 return QStringLiteral("Windows socket error %1").arg(error);
88 }
89}
90
91int listenExclusive(const QHostAddress &address, int listenQueue, quint16 port, QString *errorOut)
92{
93 if (!ensureWinsockInitialized(errorOut)) {
94 return -1;
95 }
96
97 const bool ipv6 = address.protocol() == QHostAddress::IPv6Protocol ||
98 address.protocol() == QHostAddress::AnyIPProtocol;
99
100 SOCKET socket =
101 WSASocketW(ipv6 ? AF_INET6 : AF_INET,
102 SOCK_STREAM,
103 IPPROTO_TCP,
104 nullptr,
105 0,
106 WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED);
107 if (socket == INVALID_SOCKET) {
108 if (errorOut) {
109 *errorOut = windowsSocketErrorString(WSAGetLastError());
110 }
111 return -1;
112 }
113
114 BOOL exclusive = TRUE;
115 if (setsockopt(socket,
116 SOL_SOCKET,
117 SO_EXCLUSIVEADDRUSE,
118 reinterpret_cast<const char *>(&exclusive),
119 sizeof(exclusive)) != 0) {
120 if (errorOut) {
121 *errorOut = windowsSocketErrorString(WSAGetLastError());
122 }
123 closesocket(socket);
124 return -1;
125 }
126
127 if (ipv6) {
128 sockaddr_in6 sa{};
129 sa.sin6_family = AF_INET6;
130 sa.sin6_port = htons(port);
131 if (address.protocol() == QHostAddress::AnyIPProtocol) {
132 sa.sin6_addr = in6addr_any;
133 } else {
134 const Q_IPV6ADDR tmp = address.toIPv6Address();
135 memcpy(&sa.sin6_addr, &tmp, sizeof(tmp));
136 }
137 if (bind(socket, reinterpret_cast<sockaddr *>(&sa), sizeof(sa)) != 0) {
138 if (errorOut) {
139 *errorOut = windowsSocketErrorString(WSAGetLastError());
140 }
141 closesocket(socket);
142 return -1;
143 }
144 } else {
145 sockaddr_in sa{};
146 sa.sin_family = AF_INET;
147 sa.sin_port = htons(port);
148 if (address.protocol() == QHostAddress::Any) {
149 sa.sin_addr.s_addr = INADDR_ANY;
150 } else {
151 sa.sin_addr.s_addr = htonl(address.toIPv4Address());
152 }
153 if (bind(socket, reinterpret_cast<sockaddr *>(&sa), sizeof(sa)) != 0) {
154 if (errorOut) {
155 *errorOut = windowsSocketErrorString(WSAGetLastError());
156 }
157 closesocket(socket);
158 return -1;
159 }
160 }
161
162 if (::listen(socket, listenQueue) != 0) {
163 if (errorOut) {
164 *errorOut = windowsSocketErrorString(WSAGetLastError());
165 }
166 closesocket(socket);
167 return -1;
168 }
169
170 return static_cast<int>(socket);
171}
172} // namespace
173#endif
174
175TcpServerBalancer::TcpServerBalancer(Server *server)
176 : QTcpServer(server)
177 , m_server(server)
178{
179}
180
181TcpServerBalancer::~TcpServerBalancer()
182{
183#ifndef QT_NO_SSL
184 delete m_sslConfiguration;
185#endif // QT_NO_SSL
186}
187
188bool TcpServerBalancer::listen(const QString &line, Protocol *protocol, bool secure)
189{
190 m_protocol = protocol;
191
192 int commaPos = line.indexOf(u',');
193 const QString addressPortString = line.mid(0, commaPos);
194
195 QString addressString;
196 int closeBracketPos = addressPortString.indexOf(u']');
197 if (closeBracketPos != -1) {
198 if (!line.startsWith(u'[')) {
199 std::cerr << "Failed to parse address: " << qPrintable(addressPortString) << '\n';
200 return false;
201 }
202 addressString = addressPortString.mid(1, closeBracketPos - 1);
203 } else {
204 addressString = addressPortString.section(u':', 0, -2);
205 }
206 const QString portString = addressPortString.section(u':', -1);
207
208 QHostAddress address;
209 if (addressString.isEmpty()) {
211 } else {
212 address.setAddress(addressString);
213 }
214
215 bool ok;
216 quint16 port = portString.toUInt(&ok);
217 if (!ok || (port < 1 || port > 35554)) {
218 port = 80;
219 }
220
221#ifndef QT_NO_SSL
222 if (secure) {
223 if (commaPos == -1) {
224 std::cerr << "No SSL certificate specified" << '\n';
225 return false;
226 }
227
228 const QString sslString = line.mid(commaPos + 1);
229 const QString certPath = sslString.section(u',', 0, 0);
230 QFile certFile(certPath);
231 if (!certFile.open(QFile::ReadOnly)) {
232 std::cerr << "Failed to open SSL certificate" << qPrintable(certPath)
233 << qPrintable(certFile.errorString()) << '\n';
234 return false;
235 }
236 QSslCertificate cert(&certFile);
237 if (cert.isNull()) {
238 std::cerr << "Failed to parse SSL certificate" << '\n';
239 return false;
240 }
241
242 const QString keyPath = sslString.section(u',', 1, 1);
243 QFile keyFile(keyPath);
244 if (!keyFile.open(QFile::ReadOnly)) {
245 std::cerr << "Failed to open SSL private key" << qPrintable(keyPath)
246 << qPrintable(keyFile.errorString()) << '\n';
247 return false;
248 }
249
250 QSsl::KeyAlgorithm algorithm = QSsl::Rsa;
251 const QString keyAlgorithm = sslString.section(u',', 2, 2);
252 if (!keyAlgorithm.isEmpty()) {
253 if (keyAlgorithm.compare(u"rsa", Qt::CaseInsensitive) == 0) {
254 algorithm = QSsl::Rsa;
255 } else if (keyAlgorithm.compare(u"ec", Qt::CaseInsensitive) == 0) {
256 algorithm = QSsl::Ec;
257 } else {
258 std::cerr << "Failed to select SSL Key Algorithm" << qPrintable(keyAlgorithm)
259 << '\n';
260 return false;
261 }
262 }
263
264 QSslKey key(&keyFile, algorithm);
265 if (key.isNull()) {
266 std::cerr << "Failed to parse SSL private key" << '\n';
267 return false;
268 }
269
270 m_sslConfiguration = new QSslConfiguration;
271 m_sslConfiguration->setLocalCertificate(cert);
272 m_sslConfiguration->setPrivateKey(key);
273 m_sslConfiguration->setPeerVerifyMode(
274 QSslSocket::VerifyNone); // prevent asking for client certificate
275 if (m_server->httpsH2()) {
276 m_sslConfiguration->setAllowedNextProtocols(
277 {QByteArrayLiteral("h2"), QSslConfiguration::NextProtocolHttp1_1});
278 }
279 }
280#endif // QT_NO_SSL
281
282 m_address = address;
283 m_port = port;
284 m_bindError.clear();
285
286#ifdef Q_OS_LINUX
287 int socket = listenReuse(
288 address, m_server->listenQueue(), port, m_server->reusePort(), !m_server->reusePort());
289 if (socket > 0) {
290 if (setSocketDescriptor(socket)) {
292 } else {
293 m_bindError = errorString();
294 ::close(socket);
295 qCWarning(C_SERVER_BALANCER) << "Failed to listen on TCP:" << line << m_bindError;
296 return false;
297 }
298 } else {
299 std::cerr << "Failed to listen on TCP: " << qPrintable(line) << " : "
300 << qPrintable(errorString()) << '\n';
301 return false;
302 }
303#elif defined(Q_OS_WIN)
304 int socket = listenExclusive(address, m_server->listenQueue(), port, &m_bindError);
305 if (socket > 0) {
306 if (setSocketDescriptor(socket)) {
308 } else {
309 if (m_bindError.isEmpty()) {
310 m_bindError = errorString();
311 }
312 closesocket(socket);
313 qCWarning(C_SERVER_BALANCER) << "Failed to listen on TCP:" << line << m_bindError;
314 return false;
315 }
316 } else {
317 qCWarning(C_SERVER_BALANCER) << "Failed to listen on TCP:" << line << m_bindError;
318 return false;
319 }
320#else
321 setListenBacklogSize(m_server->listenQueue());
322 bool ret = QTcpServer::listen(address, port);
323 if (ret) {
325 } else {
326 m_bindError = errorString();
327 std::cerr << "Failed to listen on TCP: " << qPrintable(line) << " : "
328 << qPrintable(m_bindError) << '\n';
329 return false;
330 }
331#endif
332
333 m_serverName = serverAddress().toString().toLatin1() + ':' + QByteArray::number(port);
334 return true;
335}
336
337namespace {
338#ifdef Q_OS_LINUX
339// UnixWare 7 redefines socket -> _socket
340inline int qt_safe_socket(int domain, int type, int protocol, int flags = 0)
341{
342 Q_ASSERT((flags & ~O_NONBLOCK) == 0);
343
344 int fd;
345# ifdef QT_THREADSAFE_CLOEXEC
346 int newtype = type | SOCK_CLOEXEC;
347 if (flags & O_NONBLOCK) {
348 newtype |= SOCK_NONBLOCK;
349 }
350 fd = ::socket(domain, newtype, protocol);
351 return fd;
352# else
353 fd = ::socket(domain, type, protocol);
354 if (fd == -1) {
355 return -1;
356 }
357
358 ::fcntl(fd, F_SETFD, FD_CLOEXEC);
359
360 // set non-block too?
361 if (flags & O_NONBLOCK) {
362 ::fcntl(fd, F_SETFL, ::fcntl(fd, F_GETFL) | O_NONBLOCK);
363 }
364
365 return fd;
366# endif
367}
368
369int createNewSocket(QAbstractSocket::NetworkLayerProtocol &socketProtocol)
370{
371 int protocol = 0;
372
373 int domain = (socketProtocol == QAbstractSocket::IPv6Protocol ||
374 socketProtocol == QAbstractSocket::AnyIPProtocol)
375 ? AF_INET6
376 : AF_INET;
377 int type = SOCK_STREAM;
378
379 int socket = qt_safe_socket(domain, type, protocol, O_NONBLOCK);
380 if (socket < 0 && socketProtocol == QAbstractSocket::AnyIPProtocol && errno == EAFNOSUPPORT) {
381 domain = AF_INET;
382 socket = qt_safe_socket(domain, type, protocol, O_NONBLOCK);
383 socketProtocol = QAbstractSocket::IPv4Protocol;
384 }
385
386 if (socket < 0) {
387 int ecopy = errno;
388 switch (ecopy) {
389 case EPROTONOSUPPORT:
390 case EAFNOSUPPORT:
391 case EINVAL:
392 qCDebug(C_SERVER_BALANCER)
393 << "setError(QAbstractSocket::UnsupportedSocketOperationError, "
394 "ProtocolUnsupportedErrorString)";
395 break;
396 case ENFILE:
397 case EMFILE:
398 case ENOBUFS:
399 case ENOMEM:
400 qCDebug(C_SERVER_BALANCER)
401 << "setError(QAbstractSocket::SocketResourceError, ResourceErrorString)";
402 break;
403 case EACCES:
404 qCDebug(C_SERVER_BALANCER)
405 << "setError(QAbstractSocket::SocketAccessError, AccessErrorString)";
406 break;
407 default:
408 break;
409 }
410
411# if defined(QNATIVESOCKETENGINE_DEBUG)
412 qCDebug(C_SERVER_BALANCER,
413 "QNativeSocketEnginePrivate::createNewSocket(%d, %d) == false (%s)",
414 socketType,
415 socketProtocol,
416 strerror(ecopy));
417# endif
418
419 return false;
420 }
421
422# if defined(QNATIVESOCKETENGINE_DEBUG)
423 qCDebug(C_SERVER_BALANCER,
424 "QNativeSocketEnginePrivate::createNewSocket(%d, %d) == true",
425 socketType,
426 socketProtocol);
427# endif
428
429 return socket;
430}
431
432union qt_sockaddr {
433 sockaddr a;
434 sockaddr_in a4;
435 sockaddr_in6 a6;
436};
437
438# define QT_SOCKLEN_T int
439# define QT_SOCKET_BIND ::bind
440
441namespace SetSALen {
442template <typename T>
443void set(T *sa, typename std::enable_if<(&T::sa_len, true), QT_SOCKLEN_T>::type len)
444{
445 sa->sa_len = len;
446}
447template <typename T>
448void set(T *sin6, typename std::enable_if<(&T::sin6_len, true), QT_SOCKLEN_T>::type len)
449{
450 sin6->sin6_len = len;
451}
452template <typename T>
453void set(T *, ...)
454{
455}
456} // namespace SetSALen
457
458void setPortAndAddress(quint16 port,
459 const QHostAddress &address,
461 qt_sockaddr *aa,
462 int *sockAddrSize)
463{
464 if (address.protocol() == QAbstractSocket::IPv6Protocol ||
466 socketProtocol == QAbstractSocket::IPv6Protocol ||
467 socketProtocol == QAbstractSocket::AnyIPProtocol) {
468 memset(&aa->a6, 0, sizeof(sockaddr_in6));
469 aa->a6.sin6_family = AF_INET6;
470 // #if QT_CONFIG(networkinterface)
471 // aa->a6.sin6_scope_id = scopeIdFromString(address.scopeId());
472 // #endif
473 aa->a6.sin6_port = htons(port);
474 Q_IPV6ADDR tmp = address.toIPv6Address();
475 memcpy(&aa->a6.sin6_addr, &tmp, sizeof(tmp));
476 *sockAddrSize = sizeof(sockaddr_in6);
477 SetSALen::set(&aa->a, sizeof(sockaddr_in6));
478 } else {
479 memset(&aa->a, 0, sizeof(sockaddr_in));
480 aa->a4.sin_family = AF_INET;
481 aa->a4.sin_port = htons(port);
482 aa->a4.sin_addr.s_addr = htonl(address.toIPv4Address());
483 *sockAddrSize = sizeof(sockaddr_in);
484 SetSALen::set(&aa->a, sizeof(sockaddr_in));
485 }
486}
487
488bool nativeBind(int socketDescriptor, const QHostAddress &address, quint16 port)
489{
490 qt_sockaddr aa;
491 int sockAddrSize;
492 setPortAndAddress(port, address, address.protocol(), &aa, &sockAddrSize);
493
494# ifdef IPV6_V6ONLY
495 if (aa.a.sa_family == AF_INET6) {
496 int ipv6only = 0;
497 if (address.protocol() == QAbstractSocket::IPv6Protocol) {
498 ipv6only = 1;
499 }
500 // default value of this socket option varies depending on unix variant (or system
501 // configuration on BSD), so always set it explicitly
502 ::setsockopt(
503 socketDescriptor, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &ipv6only, sizeof(ipv6only));
504 }
505# endif
506
507 int bindResult = ::bind(socketDescriptor, &aa.a, sockAddrSize);
508 if (bindResult < 0 && errno == EAFNOSUPPORT &&
510 // retry with v4
511 aa.a4.sin_family = AF_INET;
512 aa.a4.sin_port = htons(port);
513 aa.a4.sin_addr.s_addr = htonl(address.toIPv4Address());
514 sockAddrSize = sizeof(aa.a4);
515 bindResult = QT_SOCKET_BIND(socketDescriptor, &aa.a, sockAddrSize);
516 }
517
518 if (bindResult < 0) {
519# if defined(QNATIVESOCKETENGINE_DEBUG)
520 int ecopy = errno;
521# endif
522 // switch(errno) {
523 // case EADDRINUSE:
524 // setError(QAbstractSocket::AddressInUseError, AddressInuseErrorString);
525 // break;
526 // case EACCES:
527 // setError(QAbstractSocket::SocketAccessError, AddressProtectedErrorString);
528 // break;
529 // case EINVAL:
530 // setError(QAbstractSocket::UnsupportedSocketOperationError,
531 // OperationUnsupportedErrorString); break;
532 // case EADDRNOTAVAIL:
533 // setError(QAbstractSocket::SocketAddressNotAvailableError,
534 // AddressNotAvailableErrorString); break;
535 // default:
536 // break;
537 // }
538
539# if defined(QNATIVESOCKETENGINE_DEBUG)
540 qCDebug(C_SERVER_BALANCER,
541 "QNativeSocketEnginePrivate::nativeBind(%s, %i) == false (%s)",
542 address.toString().toLatin1().constData(),
543 port,
544 strerror(ecopy));
545# endif
546
547 return false;
548 }
549
550# if defined(QNATIVESOCKETENGINE_DEBUG)
551 qCDebug(C_SERVER_BALANCER,
552 "QNativeSocketEnginePrivate::nativeBind(%s, %i) == true",
553 address.toString().toLatin1().constData(),
554 port);
555# endif
556 // socketState = QAbstractSocket::BoundState;
557 return true;
558}
559
560int listenReuse(const QHostAddress &address,
561 int listenQueue,
562 quint16 port,
563 bool reusePort,
564 bool startListening)
565{
567
568 int socket = createNewSocket(proto);
569 if (socket < 0) {
570 qCCritical(C_SERVER_BALANCER) << "Failed to create new socket";
571 return -1;
572 }
573
574 int optval = 1;
575 // SO_REUSEADDR is set by default on QTcpServer and allows to bind again
576 // without having to wait all previous connections to close
577 if (::setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))) {
578 qCCritical(C_SERVER_BALANCER) << "Failed to set SO_REUSEADDR on socket" << socket;
579 return -1;
580 }
581
582 if (reusePort) {
583 if (::setsockopt(socket, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval))) {
584 qCCritical(C_SERVER_BALANCER) << "Failed to set SO_REUSEPORT on socket" << socket;
585 return -1;
586 }
587 }
588
589 if (!nativeBind(socket, address, port)) {
590 qCCritical(C_SERVER_BALANCER) << "Failed to bind to socket" << socket;
591 return -1;
592 }
593
594 if (startListening && ::listen(socket, listenQueue) < 0) {
595 qCCritical(C_SERVER_BALANCER) << "Failed to listen to socket" << socket;
596 return -1;
597 }
598
599 return socket;
600}
601#endif // Q_OS_LINUX
602} // namespace
603
604void TcpServerBalancer::setBalancer(bool enable)
605{
606 m_balancer = enable;
607}
608
609void TcpServerBalancer::incomingConnection(qintptr handle)
610{
611 TcpServer *serverIdle = m_servers.at(m_currentServer++ % m_servers.size());
612
613 Q_EMIT serverIdle->createConnection(handle);
614}
615
616TcpServer *TcpServerBalancer::createServer(ServerEngine *engine)
617{
618 TcpServer *server;
619 if (m_sslConfiguration) {
620#ifndef QT_NO_SSL
621 auto sslServer = new TcpSslServer(m_serverName, m_protocol, m_server, engine);
622 sslServer->setSslConfiguration(*m_sslConfiguration);
623 server = sslServer;
624#endif // QT_NO_SSL
625 } else {
626 server = new TcpServer(m_serverName, m_protocol, m_server, engine);
627 }
628 connect(engine, &ServerEngine::shutdown, server, &TcpServer::shutdown);
629
630 if (m_balancer) {
631 connect(engine, &ServerEngine::started, this, [this, server]() {
632 m_servers.push_back(server);
635 connect(server,
636 &TcpServer::createConnection,
637 server,
638 &TcpServer::incomingConnection,
640 } else {
641
642#ifdef Q_OS_LINUX
643 if (m_server->reusePort()) {
644 connect(engine, &ServerEngine::started, this, [this, server]() {
645 int socket = listenReuse(
646 m_address, m_server->listenQueue(), m_port, m_server->reusePort(), true);
647 if (!server->setSocketDescriptor(socket)) {
648 qFatal("Failed to set server socket descriptor, reuse-port");
649 }
651 return server;
652 }
653#endif
654
655 if (server->setSocketDescriptor(socketDescriptor())) {
656 server->pauseAccepting();
657 connect(engine,
658 &ServerEngine::started,
659 server,
662 } else {
663 qFatal("Failed to set server socket descriptor");
664 }
665 }
666
667 return server;
668}
669
670#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)
QString arg(Args &&... args) const const
void clear()
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
void close()
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