fastcgi++
A C++ FastCGI/Web API
sockets.cpp
Go to the documentation of this file.
1 
13 /*******************************************************************************
14 * Copyright (C) 2017 Eddie Carle [eddie@isatec.ca] *
15 * *
16 * This file is part of fastcgi++. *
17 * *
18 * fastcgi++ is free software: you can redistribute it and/or modify it under *
19 * the terms of the GNU Lesser General Public License as published by the Free *
20 * Software Foundation, either version 3 of the License, or (at your option) *
21 * any later version. *
22 * *
23 * fastcgi++ is distributed in the hope that it will be useful, but WITHOUT ANY *
24 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS *
25 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for *
26 * more details. *
27 * *
28 * You should have received a copy of the GNU Lesser General Public License *
29 * along with fastcgi++. If not, see <http://www.gnu.org/licenses/>. *
30 *******************************************************************************/
31 
32 #include "fastcgi++/sockets.hpp"
33 #include "fastcgi++/log.hpp"
34 
35 #ifdef FASTCGIPP_LINUX
36 #include <sys/epoll.h>
37 #elif defined FASTCGIPP_UNIX
38 #include <algorithm>
39 #endif
40 
41 #include <sys/socket.h>
42 #include <sys/un.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <sys/socket.h>
46 #include <netdb.h>
47 #include <unistd.h>
48 #include <fcntl.h>
49 #include <pwd.h>
50 #include <grp.h>
51 #include <cstring>
52 
54  const socket_t& socket,
55  SocketGroup& group,
56  bool valid):
57  m_data(new Data(socket, valid, group)),
58  m_original(true)
59 {
60  if(!group.pollAdd(socket))
61  {
62  ERROR_LOG("Unable to add socket " << socket << " to poll list: " \
63  << std::strerror(errno))
64  close();
65  }
66 }
67 
68 ssize_t Fastcgipp::Socket::read(char* buffer, size_t size) const
69 {
70  if(!valid())
71  return -1;
72 
73  const ssize_t count = ::read(m_data->m_socket, buffer, size);
74  if(count<0)
75  {
76  WARNING_LOG("Socket read() error on fd " \
77  << m_data->m_socket << ": " << std::strerror(errno))
78  if(errno == EAGAIN)
79  return 0;
80  close();
81  return -1;
82  }
83  if(count == 0 && m_data->m_closing)
84  {
85 #if FASTCGIPP_LOG_LEVEL > 3
86  ++m_data->m_group.m_connectionRDHupCount;
87 #endif
88  close();
89  return -1;
90  }
91 
92 #if FASTCGIPP_LOG_LEVEL > 3
93  m_data->m_group.m_bytesReceived += count;
94 #endif
95 
96  return count;
97 }
98 
99 ssize_t Fastcgipp::Socket::write(const char* buffer, size_t size) const
100 {
101  if(!valid() || m_data->m_closing)
102  return -1;
103 
104  const ssize_t count = ::send(m_data->m_socket, buffer, size, MSG_NOSIGNAL);
105  if(count<0)
106  {
107  WARNING_LOG("Socket write() error on fd " \
108  << m_data->m_socket << ": " << strerror(errno))
109  if(errno == EAGAIN)
110  return 0;
111  close();
112  return -1;
113  }
114 
115 #if FASTCGIPP_LOG_LEVEL > 3
116  m_data->m_group.m_bytesSent += count;
117 #endif
118 
119  return count;
120 }
121 
123 {
124  if(valid())
125  {
126  ::shutdown(m_data->m_socket, SHUT_RDWR);
127  ::close(m_data->m_socket);
128  m_data->m_valid = false;
129  m_data->m_group.pollDel(m_data->m_socket);
130  m_data->m_group.m_sockets.erase(m_data->m_socket);
131 #if FASTCGIPP_LOG_LEVEL > 3
132  if(!m_data->m_closing)
133  ++m_data->m_group.m_connectionKillCount;
134 #endif
135  }
136 }
137 
139 {
140  if(m_original && valid())
141  {
142  ::shutdown(m_data->m_socket, SHUT_RDWR);
143  ::close(m_data->m_socket);
144  m_data->m_valid = false;
145  m_data->m_group.pollDel(m_data->m_socket);
146  }
147 }
148 
150 #ifdef FASTCGIPP_LINUX
151  m_poll(epoll_create1(0)),
152 #endif
153  m_waking(false),
154  m_accept(true),
155  m_refreshListeners(false)
156 #if FASTCGIPP_LOG_LEVEL > 3
157  ,m_incomingConnectionCount(0),
158  m_outgoingConnectionCount(0),
159  m_connectionKillCount(0),
160  m_connectionRDHupCount(0),
161  m_bytesSent(0),
162  m_bytesReceived(0)
163 #endif
164 {
165  // Add our wakeup socket into the poll list
166  socketpair(AF_UNIX, SOCK_STREAM, 0, m_wakeSockets);
168  DIAG_LOG("SocketGroup::SocketGroup(): Initialized ")
169 }
170 
172 {
173 #ifdef FASTCGIPP_LINUX
174  close(m_poll);
175 #endif
176  close(m_wakeSockets[0]);
177  close(m_wakeSockets[1]);
178  for(const auto& listener: m_listeners)
179  {
180  ::shutdown(listener, SHUT_RDWR);
181  ::close(listener);
182  }
183  for(const auto& filename: m_filenames)
184  std::remove(filename.c_str());
185 
186  DIAG_LOG("SocketGroup::~SocketGroup(): Incoming sockets ======== " \
187  << m_incomingConnectionCount)
188  DIAG_LOG("SocketGroup::~SocketGroup(): Outgoing sockets ======== " \
189  << m_outgoingConnectionCount)
190  DIAG_LOG("SocketGroup::~SocketGroup(): Locally closed sockets == " \
191  << m_connectionKillCount)
192  DIAG_LOG("SocketGroup::~SocketGroup(): Remotely closed sockets = " \
193  << m_connectionRDHupCount)
194  DIAG_LOG("SocketGroup::~SocketGroup(): Remaining sockets ======= " \
195  << m_sockets.size())
196  DIAG_LOG("SocketGroup::~SocketGroup(): Bytes sent ===== " << m_bytesSent)
197  DIAG_LOG("SocketGroup::~SocketGroup(): Bytes received = " \
198  << m_bytesReceived)
199 }
200 
202 {
203  const int listen=0;
204 
205  fcntl(listen, F_SETFL, fcntl(listen, F_GETFL)|O_NONBLOCK);
206 
207  if(m_listeners.find(listen) == m_listeners.end())
208  {
209  if(::listen(listen, 100) < 0)
210  {
211  ERROR_LOG("Unable to listen on default FastCGI socket: "\
212  << std::strerror(errno));
213  return false;
214  }
215  m_listeners.insert(listen);
216  m_refreshListeners = true;
217  return true;
218  }
219  else
220  {
221  ERROR_LOG("Socket " << listen << " already being listened to")
222  return false;
223  }
224 }
225 
227  const char* name,
228  uint32_t permissions,
229  const char* owner,
230  const char* group)
231 {
232  if(std::remove(name) != 0 && errno != ENOENT)
233  {
234  ERROR_LOG("Unable to delete file \"" << name << "\": " \
235  << std::strerror(errno))
236  return false;
237  }
238 
239  const auto fd = socket(AF_UNIX, SOCK_STREAM, 0);
240  if(fd == -1)
241  {
242  ERROR_LOG("Unable to create unix socket: " << std::strerror(errno))
243  return false;
244 
245  }
246 
247  struct sockaddr_un address;
248  std::memset(&address, 0, sizeof(address));
249  address.sun_family = AF_UNIX;
250  std::strncpy(address.sun_path, name, sizeof(address.sun_path) - 1);
251 
252  if(bind(
253  fd,
254  reinterpret_cast<struct sockaddr*>(&address),
255  sizeof(address)) < 0)
256  {
257  ERROR_LOG("Unable to bind to unix socket \"" << name << "\": " \
258  << std::strerror(errno));
259  close(fd);
260  std::remove(name);
261  return false;
262  }
263 
264  // Set the user and group of the socket
265  if(owner!=nullptr && group!=nullptr)
266  {
267  struct passwd* passwd = getpwnam(owner);
268  struct group* grp = getgrnam(group);
269  if(fchown(fd, passwd->pw_uid, grp->gr_gid)==-1)
270  {
271  ERROR_LOG("Unable to chown " << owner << ":" << group \
272  << " on the unix socket \"" << name << "\": " \
273  << std::strerror(errno));
274  close(fd);
275  return false;
276  }
277  }
278 
279  // Set the user and group of the socket
280  if(permissions != 0xffffffffUL)
281  {
282  if(fchmod(fd, permissions)<0)
283  {
284  ERROR_LOG("Unable to set permissions 0" << std::oct << permissions \
285  << std::dec << " on \"" << name << "\": " \
286  << std::strerror(errno));
287  close(fd);
288  return false;
289  }
290  }
291 
292  if(::listen(fd, 100) < 0)
293  {
294  ERROR_LOG("Unable to listen on unix socket :\"" << name << "\": "\
295  << std::strerror(errno));
296  close(fd);
297  return false;
298  }
299 
300  m_filenames.emplace_back(name);
301  m_listeners.insert(fd);
302  m_refreshListeners = true;
303  return true;
304 }
305 
307  const char* interface,
308  const char* service)
309 {
310  if(service == nullptr)
311  {
312  ERROR_LOG("Cannot call listen(interface, service) with service=nullptr.")
313  return false;
314  }
315 
316  addrinfo hints;
317  std::memset(&hints, 0, sizeof(addrinfo));
318  hints.ai_family = AF_UNSPEC;
319  hints.ai_socktype = SOCK_STREAM;
320  hints.ai_flags = AI_PASSIVE;
321  hints.ai_protocol = IPPROTO_TCP;
322  hints.ai_canonname = nullptr;
323  hints.ai_addr = nullptr;
324  hints.ai_next = nullptr;
325 
326  addrinfo* result;
327 
328  if(getaddrinfo(interface, service, &hints, &result))
329  {
330  ERROR_LOG("Unable to use getaddrinfo() on " \
331  << (interface==nullptr?"0.0.0.0":interface) << ":" << service << ". " \
332  << std::strerror(errno))
333  return false;
334  }
335 
336  int fd=-1;
337  for(auto i=result; i!=nullptr; i=result->ai_next)
338  {
339  fd = socket(i->ai_family, i->ai_socktype, i->ai_protocol);
340  if(fd == -1)
341  continue;
342  if(
343  bind(fd, i->ai_addr, i->ai_addrlen) == 0
344  && ::listen(fd, 100) == 0)
345  break;
346  close(fd);
347  fd = -1;
348  }
349  freeaddrinfo(result);
350 
351  if(fd==-1)
352  {
353  ERROR_LOG("Unable to bind/listen on " \
354  << (interface==nullptr?"0.0.0.0":interface) << ":" << service)
355  return false;
356  }
357 
358  m_listeners.insert(fd);
359  m_refreshListeners = true;
360  return true;
361 }
362 
364 {
365  const auto fd = socket(AF_UNIX, SOCK_STREAM, 0);
366  if(fd == -1)
367  {
368  ERROR_LOG("Unable to create unix socket: " << std::strerror(errno))
369  return Socket();
370  }
371 
372  sockaddr_un address;
373  std::memset(&address, 0, sizeof(address));
374  address.sun_family = AF_UNIX;
375  std::strncpy(address.sun_path, name, sizeof(address.sun_path) - 1);
376 
377  if(::connect(
378  fd,
379  reinterpret_cast<struct sockaddr*>(&address),
380  sizeof(address))==-1)
381  {
382  ERROR_LOG("Unable to connect to unix socket \"" << name << "\": " \
383  << std::strerror(errno));
384  close(fd);
385  return Socket();
386  }
387 
388 #if FASTCGIPP_LOG_LEVEL > 3
389  ++m_outgoingConnectionCount;
390 #endif
391 
392  return m_sockets.emplace(
393  fd,
394  Socket(fd, *this)).first->second;
395 }
396 
398  const char* host,
399  const char* service)
400 {
401  if(service == nullptr)
402  {
403  ERROR_LOG("Cannot call connect(host, service) with service=nullptr.")
404  return Socket();
405  }
406 
407  if(host == nullptr)
408  {
409  ERROR_LOG("Cannot call host(host, service) with host=nullptr.")
410  return Socket();
411  }
412 
413  addrinfo hints;
414  std::memset(&hints, 0, sizeof(addrinfo));
415  hints.ai_family = AF_UNSPEC;
416  hints.ai_socktype = SOCK_STREAM;
417  hints.ai_flags = 0;
418  hints.ai_protocol = IPPROTO_TCP;
419  hints.ai_canonname = nullptr;
420  hints.ai_addr = nullptr;
421  hints.ai_next = nullptr;
422 
423  addrinfo* result;
424 
425  if(getaddrinfo(host, service, &hints, &result))
426  {
427  ERROR_LOG("Unable to use getaddrinfo() on " << host << ":" << service \
428  << ". " << std::strerror(errno))
429  return Socket();
430  }
431 
432  int fd=-1;
433  for(auto i=result; i!=nullptr; i=result->ai_next)
434  {
435  fd = socket(i->ai_family, i->ai_socktype, i->ai_protocol);
436  if(fd == -1)
437  continue;
438  if(::connect(fd, i->ai_addr, i->ai_addrlen) != -1)
439  break;
440  close(fd);
441  fd = -1;
442  }
443  freeaddrinfo(result);
444 
445  if(fd==-1)
446  {
447  ERROR_LOG("Unable to connect to " << host << ":" << service)
448  return Socket();
449  }
450 
451 #if FASTCGIPP_LOG_LEVEL > 3
452  ++m_outgoingConnectionCount;
453 #endif
454 
455  return m_sockets.emplace(
456  fd,
457  Socket(fd, *this)).first->second;
458 }
459 
461 {
462  int pollResult;
463 
464 #ifdef FASTCGIPP_LINUX
465  epoll_event epollEvent;
466  const auto& pollIn = EPOLLIN;
467  const auto& pollErr = EPOLLERR;
468  const auto& pollHup = EPOLLHUP;
469  const auto& pollRdHup = EPOLLRDHUP;
470 #elif defined FASTCGIPP_UNIX
471  const auto& pollIn = POLLIN;
472  const auto& pollErr = POLLERR;
473  const auto& pollHup = POLLHUP;
474  const auto& pollRdHup = POLLRDHUP;
475 #endif
476 
477  while(m_listeners.size()+m_sockets.size() > 0)
478  {
479  if(m_refreshListeners)
480  {
481  for(auto& listener: m_listeners)
482  {
483  pollDel(listener);
484  if(m_accept && !pollAdd(listener))
485  FAIL_LOG("Unable to add listen socket " << listener \
486  << " to the poll list: " << std::strerror(errno))
487  }
488  m_refreshListeners=false;
489  }
490 #ifdef FASTCGIPP_LINUX
491  pollResult = epoll_wait(
492  m_poll,
493  &epollEvent,
494  1,
495  block?-1:0);
496 #elif defined FASTCGIPP_UNIX
497  pollResult = ::poll(
498  m_poll.data(),
499  m_poll.size(),
500  block?-1:0);
501 #endif
502 
503  if(pollResult<0)
504  {
505  if(errno == EINTR)
506  continue;
507  FAIL_LOG("Error on poll: " << std::strerror(errno))
508  }
509  else if(pollResult>0)
510  {
511 #ifdef FASTCGIPP_LINUX
512  const auto& socketId = epollEvent.data.fd;
513  const auto& events = epollEvent.events;
514 #elif defined FASTCGIPP_UNIX
515  const auto fd = std::find_if(
516  m_poll.begin(),
517  m_poll.end(),
518  [] (const pollfd& x)
519  {
520  return x.revents != 0;
521  });
522  if(fd == m_poll.end())
523  FAIL_LOG("poll() gave a result >0 but no revents are non-zero")
524  const auto& socketId = fd->fd;
525  const auto& events = fd->revents;
526 #endif
527 
528  if(m_listeners.find(socketId) != m_listeners.end())
529  {
530  if(events == pollIn)
531  {
532  createSocket(socketId);
533  continue;
534  }
535  else if(events & pollErr)
536  FAIL_LOG("Error in listen socket.")
537  else if(events & (pollHup | pollRdHup))
538  FAIL_LOG("The listen socket hung up.")
539  else
540  FAIL_LOG("Got a weird event 0x" << std::hex << events\
541  << " on listen poll." )
542  }
543  else if(socketId == m_wakeSockets[1])
544  {
545  if(events == pollIn)
546  {
547  std::lock_guard<std::mutex> lock(m_wakingMutex);
548  char x[256];
549  if(read(m_wakeSockets[1], x, 256)<1)
550  FAIL_LOG("Unable to read out of wakeup socket: " << \
551  std::strerror(errno))
552  m_waking=false;
553  block=false;
554  continue;
555  }
556  else if(events & (pollHup | pollRdHup))
557  FAIL_LOG("The wakeup socket hung up.")
558  else if(events & pollErr)
559  FAIL_LOG("Error in the wakeup socket.")
560  }
561  else
562  {
563  const auto socket = m_sockets.find(socketId);
564  if(socket == m_sockets.end())
565  {
566  ERROR_LOG("Poll gave fd " << socketId \
567  << " which isn't in m_sockets.")
568  pollDel(socketId);
569  close(socketId);
570  continue;
571  }
572 
573  if(events & pollRdHup)
574  socket->second.m_data->m_closing=true;
575  else if(events & pollHup)
576  {
577  WARNING_LOG("Socket " << socketId << " hung up")
578  socket->second.m_data->m_closing=true;
579  }
580  else if(events & pollErr)
581  {
582  ERROR_LOG("Error in socket " << socketId)
583  socket->second.m_data->m_closing=true;
584  }
585  else if((events & pollIn) == 0)
586  FAIL_LOG("Got a weird event 0x" << std::hex << events\
587  << " on socket poll." )
588  return socket->second;
589  }
590  }
591  break;
592  }
593  return Socket();
594 }
595 
597 {
598  std::lock_guard<std::mutex> lock(m_wakingMutex);
599  if(!m_waking)
600  {
601  m_waking=true;
602  char x=0;
603  if(write(m_wakeSockets[0], &x, 1) != 1)
604  FAIL_LOG("Unable to write to wakeup socket: " \
605  << std::strerror(errno))
606  }
607 }
608 
610 {
611  sockaddr_un addr;
612  socklen_t addrlen=sizeof(sockaddr_un);
613  const socket_t socket=::accept(
614  listener,
615  reinterpret_cast<sockaddr*>(&addr),
616  &addrlen);
617  if(socket<0)
618  FAIL_LOG("Unable to accept() with fd " \
619  << listener << ": " \
620  << std::strerror(errno))
621  if(fcntl(
622  socket,
623  F_SETFL,
624  fcntl(socket, F_GETFL)|O_NONBLOCK)
625  < 0)
626  {
627  ERROR_LOG("Unable to set NONBLOCK on fd " << socket \
628  << " with fcntl(): " << std::strerror(errno))
629  close(socket);
630  return;
631  }
632 
633  if(m_accept)
634  {
635  m_sockets.emplace(
636  socket,
637  Socket(socket, *this));
638 #if FASTCGIPP_LOG_LEVEL > 3
639  ++m_incomingConnectionCount;
640 #endif
641  }
642  else
643  close(socket);
644 }
645 
647  m_data(new Data(-1, false, *static_cast<SocketGroup*>(nullptr))),
648  m_original(false)
649 {}
650 
652 {
653 #ifdef FASTCGIPP_LINUX
654  epoll_event event;
655  event.data.fd = socket;
656  event.events = EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLRDHUP;
657  return epoll_ctl(m_poll, EPOLL_CTL_ADD, socket, &event) != -1;
658 #elif defined FASTCGIPP_UNIX
659  const auto fd = std::find_if(
660  m_poll.begin(),
661  m_poll.end(),
662  [&socket] (const pollfd& x)
663  {
664  return x.fd == socket;
665  });
666  if(fd != m_poll.end())
667  return false;
668 
669  m_poll.emplace_back();
670  m_poll.back().fd = socket;
671  m_poll.back().events = POLLIN | POLLRDHUP | POLLERR | POLLHUP;
672  return true;
673 #endif
674 }
675 
677 {
678 #ifdef FASTCGIPP_LINUX
679  return epoll_ctl(m_poll, EPOLL_CTL_DEL, socket, nullptr) != -1;
680 #elif defined FASTCGIPP_UNIX
681  const auto fd = std::find_if(
682  m_poll.begin(),
683  m_poll.end(),
684  [&socket] (const pollfd& x)
685  {
686  return x.fd == socket;
687  });
688  if(fd == m_poll.end())
689  return false;
690 
691  m_poll.erase(fd);
692  return true;
693 #endif
694 }
695 
697 {
698  if(status != m_accept)
699  {
700  m_refreshListeners = true;
701  m_accept = status;
702  wake();
703  }
704 }
#define FASTCGIPP_LINUX
Definition: config.hpp:5
socket_t m_wakeSockets[2]
A pair of sockets for wakeup purposes.
Definition: sockets.hpp:429
void close() const
Call this to close the socket.
Definition: sockets.cpp:122
~Socket()
Calls close() on the socket if we are destructing the original.
Definition: sockets.cpp:138
bool listen()
Listen to the default Fastcgi socket.
Definition: sockets.cpp:201
void accept(bool status)
Should we accept new connections?
Definition: sockets.cpp:696
#define FASTCGIPP_LOG_LEVEL
Definition: config.hpp:7
void createSocket(const socket_t listener)
Accept a new connection and create it&#39;s socket.
Definition: sockets.cpp:609
int socket_t
Our socket identifier type in GNU/Linux is simply an int.
Definition: sockets.hpp:56
bool pollDel(const socket_t socket)
Remove a socket identifier to the poll list.
Definition: sockets.cpp:676
void wake()
Wake up from a nap inside poll()
Definition: sockets.cpp:596
ssize_t read(char *buffer, size_t size) const
Try and read a chunk of data out of the socket.
Definition: sockets.cpp:68
Class for representing an OS level I/O socket.
Definition: sockets.hpp:83
Socket poll(bool block)
Poll socket set for new incoming connections and data.
Definition: sockets.cpp:460
ssize_t write(const char *buffer, size_t size) const
Try and write a chunk of data into the socket.
Definition: sockets.cpp:99
bool pollAdd(const socket_t socket)
Add a socket identifier to the poll list.
Definition: sockets.cpp:651
Class for representing an OS level socket that listens for connections.
Definition: sockets.hpp:287
Data structure to hold the shared socket data.
Definition: sockets.hpp:90
#define DIAG_LOG(data)
Definition: log.hpp:158
#define FAIL_LOG(data)
Log any "errors" that cannot be recovered from and then exit.
Definition: log.hpp:91
#define ERROR_LOG(data)
Log any "errors" that can be recovered from.
Definition: log.hpp:107
Declares the Fastcgipp debugging/logging facilities.
Declares everything for interfaces with OS level sockets.
Socket connect(const char *name)
Connect to a named socket.
Definition: sockets.cpp:363
Socket()
Creates an invalid socket with no original.
Definition: sockets.cpp:646
#define WARNING_LOG(data)
Log any externally caused "errors".
Definition: log.hpp:124