cutelyst  4.6.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 *wsgi)
38  : QTcpServer(wsgi)
39  , m_wsgi(wsgi)
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_wsgi->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_wsgi->listenQueue(), port, m_wsgi->reusePort(), !m_wsgi->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  bool ret = QTcpServer::listen(address, port);
159  if (ret) {
160  pauseAccepting();
161  } else {
162  std::cerr << "Failed to listen on TCP: " << qPrintable(line) << " : "
163  << qPrintable(errorString()) << std::endl;
164  return false;
165  }
166 #endif
167 
168  m_serverName = serverAddress().toString().toLatin1() + ':' + QByteArray::number(port);
169  return true;
170 }
171 
172 #ifdef Q_OS_LINUX
173 // UnixWare 7 redefines socket -> _socket
174 static inline int qt_safe_socket(int domain, int type, int protocol, int flags = 0)
175 {
176  Q_ASSERT((flags & ~O_NONBLOCK) == 0);
177 
178  int fd;
179 # ifdef QT_THREADSAFE_CLOEXEC
180  int newtype = type | SOCK_CLOEXEC;
181  if (flags & O_NONBLOCK)
182  newtype |= SOCK_NONBLOCK;
183  fd = ::socket(domain, newtype, protocol);
184  return fd;
185 # else
186  fd = ::socket(domain, type, protocol);
187  if (fd == -1)
188  return -1;
189 
190  ::fcntl(fd, F_SETFD, FD_CLOEXEC);
191 
192  // set non-block too?
193  if (flags & O_NONBLOCK)
194  ::fcntl(fd, F_SETFL, ::fcntl(fd, F_GETFL) | O_NONBLOCK);
195 
196  return fd;
197 # endif
198 }
199 
200 int createNewSocket(QAbstractSocket::NetworkLayerProtocol &socketProtocol)
201 {
202  int protocol = 0;
203 
204  int domain = (socketProtocol == QAbstractSocket::IPv6Protocol ||
205  socketProtocol == QAbstractSocket::AnyIPProtocol)
206  ? AF_INET6
207  : AF_INET;
208  int type = SOCK_STREAM;
209 
210  int socket = qt_safe_socket(domain, type, protocol, O_NONBLOCK);
211  if (socket < 0 && socketProtocol == QAbstractSocket::AnyIPProtocol && errno == EAFNOSUPPORT) {
212  domain = AF_INET;
213  socket = qt_safe_socket(domain, type, protocol, O_NONBLOCK);
214  socketProtocol = QAbstractSocket::IPv4Protocol;
215  }
216 
217  if (socket < 0) {
218  int ecopy = errno;
219  switch (ecopy) {
220  case EPROTONOSUPPORT:
221  case EAFNOSUPPORT:
222  case EINVAL:
223  qCDebug(C_SERVER_BALANCER)
224  << "setError(QAbstractSocket::UnsupportedSocketOperationError, "
225  "ProtocolUnsupportedErrorString)";
226  break;
227  case ENFILE:
228  case EMFILE:
229  case ENOBUFS:
230  case ENOMEM:
231  qCDebug(C_SERVER_BALANCER)
232  << "setError(QAbstractSocket::SocketResourceError, ResourceErrorString)";
233  break;
234  case EACCES:
235  qCDebug(C_SERVER_BALANCER)
236  << "setError(QAbstractSocket::SocketAccessError, AccessErrorString)";
237  break;
238  default:
239  break;
240  }
241 
242 # if defined(QNATIVESOCKETENGINE_DEBUG)
243  qCDebug(C_SERVER_BALANCER,
244  "QNativeSocketEnginePrivate::createNewSocket(%d, %d) == false (%s)",
245  socketType,
246  socketProtocol,
247  strerror(ecopy));
248 # endif
249 
250  return false;
251  }
252 
253 # if defined(QNATIVESOCKETENGINE_DEBUG)
254  qCDebug(C_SERVER_BALANCER,
255  "QNativeSocketEnginePrivate::createNewSocket(%d, %d) == true",
256  socketType,
257  socketProtocol);
258 # endif
259 
260  return socket;
261 }
262 
263 union qt_sockaddr {
264  sockaddr a;
265  sockaddr_in a4;
266  sockaddr_in6 a6;
267 };
268 
269 # define QT_SOCKLEN_T int
270 # define QT_SOCKET_BIND ::bind
271 
272 namespace {
273 namespace SetSALen {
274 template <typename T>
275 void set(T *sa, typename std::enable_if<(&T::sa_len, true), QT_SOCKLEN_T>::type len)
276 {
277  sa->sa_len = len;
278 }
279 template <typename T>
280 void set(T *sin6, typename std::enable_if<(&T::sin6_len, true), QT_SOCKLEN_T>::type len)
281 {
282  sin6->sin6_len = len;
283 }
284 template <typename T>
285 void set(T *, ...)
286 {
287 }
288 } // namespace SetSALen
289 } // namespace
290 
291 void setPortAndAddress(quint16 port,
292  const QHostAddress &address,
294  qt_sockaddr *aa,
295  int *sockAddrSize)
296 {
297  if (address.protocol() == QAbstractSocket::IPv6Protocol ||
299  socketProtocol == QAbstractSocket::IPv6Protocol ||
300  socketProtocol == QAbstractSocket::AnyIPProtocol) {
301  memset(&aa->a6, 0, sizeof(sockaddr_in6));
302  aa->a6.sin6_family = AF_INET6;
303  // #if QT_CONFIG(networkinterface)
304  // aa->a6.sin6_scope_id = scopeIdFromString(address.scopeId());
305  // #endif
306  aa->a6.sin6_port = htons(port);
307  Q_IPV6ADDR tmp = address.toIPv6Address();
308  memcpy(&aa->a6.sin6_addr, &tmp, sizeof(tmp));
309  *sockAddrSize = sizeof(sockaddr_in6);
310  SetSALen::set(&aa->a, sizeof(sockaddr_in6));
311  } else {
312  memset(&aa->a, 0, sizeof(sockaddr_in));
313  aa->a4.sin_family = AF_INET;
314  aa->a4.sin_port = htons(port);
315  aa->a4.sin_addr.s_addr = htonl(address.toIPv4Address());
316  *sockAddrSize = sizeof(sockaddr_in);
317  SetSALen::set(&aa->a, sizeof(sockaddr_in));
318  }
319 }
320 
321 bool nativeBind(int socketDescriptor, const QHostAddress &address, quint16 port)
322 {
323  qt_sockaddr aa;
324  int sockAddrSize;
325  setPortAndAddress(port, address, address.protocol(), &aa, &sockAddrSize);
326 
327 # ifdef IPV6_V6ONLY
328  if (aa.a.sa_family == AF_INET6) {
329  int ipv6only = 0;
330  if (address.protocol() == QAbstractSocket::IPv6Protocol)
331  ipv6only = 1;
332  // default value of this socket option varies depending on unix variant (or system
333  // configuration on BSD), so always set it explicitly
334  ::setsockopt(
335  socketDescriptor, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &ipv6only, sizeof(ipv6only));
336  }
337 # endif
338 
339  int bindResult = ::bind(socketDescriptor, &aa.a, sockAddrSize);
340  if (bindResult < 0 && errno == EAFNOSUPPORT &&
342  // retry with v4
343  aa.a4.sin_family = AF_INET;
344  aa.a4.sin_port = htons(port);
345  aa.a4.sin_addr.s_addr = htonl(address.toIPv4Address());
346  sockAddrSize = sizeof(aa.a4);
347  bindResult = QT_SOCKET_BIND(socketDescriptor, &aa.a, sockAddrSize);
348  }
349 
350  if (bindResult < 0) {
351 # if defined(QNATIVESOCKETENGINE_DEBUG)
352  int ecopy = errno;
353 # endif
354  // switch(errno) {
355  // case EADDRINUSE:
356  // setError(QAbstractSocket::AddressInUseError, AddressInuseErrorString);
357  // break;
358  // case EACCES:
359  // setError(QAbstractSocket::SocketAccessError, AddressProtectedErrorString);
360  // break;
361  // case EINVAL:
362  // setError(QAbstractSocket::UnsupportedSocketOperationError,
363  // OperationUnsupportedErrorString); break;
364  // case EADDRNOTAVAIL:
365  // setError(QAbstractSocket::SocketAddressNotAvailableError,
366  // AddressNotAvailableErrorString); break;
367  // default:
368  // break;
369  // }
370 
371 # if defined(QNATIVESOCKETENGINE_DEBUG)
372  qCDebug(C_SERVER_BALANCER,
373  "QNativeSocketEnginePrivate::nativeBind(%s, %i) == false (%s)",
374  address.toString().toLatin1().constData(),
375  port,
376  strerror(ecopy));
377 # endif
378 
379  return false;
380  }
381 
382 # if defined(QNATIVESOCKETENGINE_DEBUG)
383  qCDebug(C_SERVER_BALANCER,
384  "QNativeSocketEnginePrivate::nativeBind(%s, %i) == true",
385  address.toString().toLatin1().constData(),
386  port);
387 # endif
388  // socketState = QAbstractSocket::BoundState;
389  return true;
390 }
391 
392 int listenReuse(const QHostAddress &address,
393  int listenQueue,
394  quint16 port,
395  bool reusePort,
396  bool startListening)
397 {
399 
400  int socket = createNewSocket(proto);
401  if (socket < 0) {
402  qCCritical(C_SERVER_BALANCER) << "Failed to create new socket";
403  return -1;
404  }
405 
406  int optval = 1;
407  // SO_REUSEADDR is set by default on QTcpServer and allows to bind again
408  // without having to wait all previous connections to close
409  if (::setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))) {
410  qCCritical(C_SERVER_BALANCER) << "Failed to set SO_REUSEADDR on socket" << socket;
411  return -1;
412  }
413 
414  if (reusePort) {
415  if (::setsockopt(socket, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval))) {
416  qCCritical(C_SERVER_BALANCER) << "Failed to set SO_REUSEPORT on socket" << socket;
417  return -1;
418  }
419  }
420 
421  if (!nativeBind(socket, address, port)) {
422  qCCritical(C_SERVER_BALANCER) << "Failed to bind to socket" << socket;
423  return -1;
424  }
425 
426  if (startListening && ::listen(socket, listenQueue) < 0) {
427  qCCritical(C_SERVER_BALANCER) << "Failed to listen to socket" << socket;
428  return -1;
429  }
430 
431  return socket;
432 }
433 #endif // Q_OS_LINUX
434 
435 void TcpServerBalancer::setBalancer(bool enable)
436 {
437  m_balancer = enable;
438 }
439 
440 void TcpServerBalancer::incomingConnection(qintptr handle)
441 {
442  TcpServer *serverIdle = m_servers.at(m_currentServer++ % m_servers.size());
443 
444  Q_EMIT serverIdle->createConnection(handle);
445 }
446 
447 TcpServer *TcpServerBalancer::createServer(ServerEngine *engine)
448 {
449  TcpServer *server;
450  if (m_sslConfiguration) {
451 #ifndef QT_NO_SSL
452  auto sslServer = new TcpSslServer(m_serverName, m_protocol, m_wsgi, engine);
453  sslServer->setSslConfiguration(*m_sslConfiguration);
454  server = sslServer;
455 #endif // QT_NO_SSL
456  } else {
457  server = new TcpServer(m_serverName, m_protocol, m_wsgi, engine);
458  }
459  connect(engine, &ServerEngine::shutdown, server, &TcpServer::shutdown);
460 
461  if (m_balancer) {
462  connect(engine, &ServerEngine::started, this, [this, server]() {
463  m_servers.push_back(server);
464  resumeAccepting();
466  connect(server,
467  &TcpServer::createConnection,
468  server,
469  &TcpServer::incomingConnection,
471  } else {
472 
473 #ifdef Q_OS_LINUX
474  if (m_wsgi->reusePort()) {
475  connect(engine, &ServerEngine::started, this, [this, server]() {
476  int socket = listenReuse(
477  m_address, m_wsgi->listenQueue(), m_port, m_wsgi->reusePort(), true);
478  if (!server->setSocketDescriptor(socket)) {
479  qFatal("Failed to set server socket descriptor, reuse-port");
480  }
482  return server;
483  }
484 #endif
485 
486  if (server->setSocketDescriptor(socketDescriptor())) {
487  server->pauseAccepting();
488  connect(engine,
489  &ServerEngine::started,
490  server,
493  } else {
494  qFatal("Failed to set server socket descriptor");
495  }
496  }
497 
498  return server;
499 }
500 
501 #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, QString::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