cutelyst  4.9.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 
25 Q_LOGGING_CATEGORY(C_SERVER_BALANCER, "cutelyst.server.tcpbalancer", QtWarningMsg)
26 
27 using namespace Cutelyst;
28 
29 #ifdef Q_OS_LINUX
30 int listenReuse(const QHostAddress &address,
31  int listenQueue,
32  quint16 port,
33  bool reusePort,
34  bool startListening);
35 #endif
36 
37 TcpServerBalancer::TcpServerBalancer(Server *server)
38  : QTcpServer(server)
39  , m_server(server)
40 {
41 }
42 
43 TcpServerBalancer::~TcpServerBalancer()
44 {
45 #ifndef QT_NO_SSL
46  delete m_sslConfiguration;
47 #endif // QT_NO_SSL
48 }
49 
50 bool 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()) {
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)) {
151  pauseAccepting();
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) {
161  pauseAccepting();
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
175 static 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 
201 int 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 
264 union 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 
273 namespace {
274 namespace SetSALen {
275 template <typename T>
276 void set(T *sa, typename std::enable_if<(&T::sa_len, true), QT_SOCKLEN_T>::type len)
277 {
278  sa->sa_len = len;
279 }
280 template <typename T>
281 void set(T *sin6, typename std::enable_if<(&T::sin6_len, true), QT_SOCKLEN_T>::type len)
282 {
283  sin6->sin6_len = len;
284 }
285 template <typename T>
286 void set(T *, ...)
287 {
288 }
289 } // namespace SetSALen
290 } // namespace
291 
292 void 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 
322 bool 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;
331  if (address.protocol() == QAbstractSocket::IPv6Protocol)
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 
393 int 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 
436 void TcpServerBalancer::setBalancer(bool enable)
437 {
438  m_balancer = enable;
439 }
440 
441 void 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 
448 TcpServer *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);
465  resumeAccepting();
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"
void setLocalCertificate(const QSslCertificate &certificate)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool setSocketDescriptor(qintptr socketDescriptor)
Implements a web server.
Definition: server.h:59
QString toString() const const
qintptr socketDescriptor() const const
bool setAddress(const QString &address)
void resumeAccepting()
void setPrivateKey(const QSslKey &key)
QHostAddress serverAddress() const const
QByteArray number(double n, char format, int precision)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
CaseInsensitive
void pauseAccepting()
bool listen(const QHostAddress &address, quint16 port)
bool isEmpty() const const
const char * constData() const const
The Cutelyst namespace holds all public Cutelyst API.
void setAllowedNextProtocols(const QList< QByteArray > &protocols)
QString errorString() const const
quint32 toIPv4Address(bool *ok) const const
Q_IPV6ADDR toIPv6Address() const const
QByteArray toLatin1() const const
QString mid(qsizetype position, qsizetype n) const const
void setPeerVerifyMode(QSslSocket::PeerVerifyMode mode)
int protocol() const const
QString section(QChar sep, qsizetype start, qsizetype end, SectionFlags flags) const const
KeyAlgorithm
QueuedConnection
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
Q_EMITQ_EMIT
uint toUInt(bool *ok, int base) const const
void setListenBacklogSize(int size)