cutelyst  5.0.1
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 namespace {
31 int listenReuse(const QHostAddress &address,
32  int listenQueue,
33  quint16 port,
34  bool reusePort,
35  bool startListening);
36 }
37 #endif
38 
39 TcpServerBalancer::TcpServerBalancer(Server *server)
40  : QTcpServer(server)
41  , m_server(server)
42 {
43 }
44 
45 TcpServerBalancer::~TcpServerBalancer()
46 {
47 #ifndef QT_NO_SSL
48  delete m_sslConfiguration;
49 #endif // QT_NO_SSL
50 }
51 
52 bool TcpServerBalancer::listen(const QString &line, Protocol *protocol, bool secure)
53 {
54  m_protocol = protocol;
55 
56  int commaPos = line.indexOf(u',');
57  const QString addressPortString = line.mid(0, commaPos);
58 
59  QString addressString;
60  int closeBracketPos = addressPortString.indexOf(u']');
61  if (closeBracketPos != -1) {
62  if (!line.startsWith(u'[')) {
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(u':', 0, -2);
69  }
70  const QString portString = addressPortString.section(u':', -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(u',', 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(u',', 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(u',', 2, 2);
116  if (!keyAlgorithm.isEmpty()) {
117  if (keyAlgorithm.compare(u"rsa", Qt::CaseInsensitive) == 0) {
118  algorithm = QSsl::Rsa;
119  } else if (keyAlgorithm.compare(u"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)) {
153  pauseAccepting();
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) {
163  pauseAccepting();
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 
175 namespace {
176 #ifdef Q_OS_LINUX
177 // UnixWare 7 redefines socket -> _socket
178 inline 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 
207 int 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 
270 union 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 
279 namespace SetSALen {
280 template <typename T>
281 void set(T *sa, typename std::enable_if<(&T::sa_len, true), QT_SOCKLEN_T>::type len)
282 {
283  sa->sa_len = len;
284 }
285 template <typename T>
286 void set(T *sin6, typename std::enable_if<(&T::sin6_len, true), QT_SOCKLEN_T>::type len)
287 {
288  sin6->sin6_len = len;
289 }
290 template <typename T>
291 void set(T *, ...)
292 {
293 }
294 } // namespace SetSALen
295 
296 void 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 
326 bool 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 
398 int 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 
442 void TcpServerBalancer::setBalancer(bool enable)
443 {
444  m_balancer = enable;
445 }
446 
447 void 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 
454 TcpServer *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);
471  resumeAccepting();
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"
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)