diff options
Diffstat (limited to '')
-rw-r--r-- | lib/base/socket.cpp | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/lib/base/socket.cpp b/lib/base/socket.cpp new file mode 100644 index 0000000..4c967de --- /dev/null +++ b/lib/base/socket.cpp @@ -0,0 +1,430 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "base/socket.hpp" +#include "base/objectlock.hpp" +#include "base/utility.hpp" +#include "base/exception.hpp" +#include "base/logger.hpp" +#include <sstream> +#include <iostream> +#include <boost/exception/errinfo_api_function.hpp> +#include <boost/exception/errinfo_errno.hpp> +#include <socketpair.h> + +#ifndef _WIN32 +# include <poll.h> +#endif /* _WIN32 */ + +using namespace icinga; + +/** + * Constructor for the Socket class. + */ +Socket::Socket(SOCKET fd) +{ + SetFD(fd); +} + +/** + * Destructor for the Socket class. + */ +Socket::~Socket() +{ + Close(); +} + +/** + * Sets the file descriptor for this socket object. + * + * @param fd The file descriptor. + */ +void Socket::SetFD(SOCKET fd) +{ + if (fd != INVALID_SOCKET) { +#ifndef _WIN32 + /* mark the socket as close-on-exec */ + Utility::SetCloExec(fd); +#endif /* _WIN32 */ + } + + ObjectLock olock(this); + m_FD = fd; +} + +/** + * Retrieves the file descriptor for this socket object. + * + * @returns The file descriptor. + */ +SOCKET Socket::GetFD() const +{ + ObjectLock olock(this); + + return m_FD; +} + +/** + * Closes the socket. + */ +void Socket::Close() +{ + ObjectLock olock(this); + + if (m_FD != INVALID_SOCKET) { + closesocket(m_FD); + m_FD = INVALID_SOCKET; + } +} + +/** + * Retrieves the last error that occurred for the socket. + * + * @returns An error code. + */ +int Socket::GetError() const +{ + int opt; + socklen_t optlen = sizeof(opt); + + int rc = getsockopt(GetFD(), SOL_SOCKET, SO_ERROR, (char *)&opt, &optlen); + + if (rc >= 0) + return opt; + + return 0; +} + +/** + * Formats a sockaddr in a human-readable way. + * + * @returns A pair of host and service. + */ +String Socket::GetHumanReadableAddress(const std::pair<String, String>& socketDetails) +{ + std::ostringstream s; + s << "[" << socketDetails.first << "]:" << socketDetails.second; + return s.str(); +} + +/** + * Returns host and service as pair. + * + * @returns A pair with host and service. + */ +std::pair<String, String> Socket::GetDetailsFromSockaddr(sockaddr *address, socklen_t len) +{ + char host[NI_MAXHOST]; + char service[NI_MAXSERV]; + + if (getnameinfo(address, len, host, sizeof(host), service, + sizeof(service), NI_NUMERICHOST | NI_NUMERICSERV) < 0) { +#ifndef _WIN32 + Log(LogCritical, "Socket") + << "getnameinfo() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("getnameinfo") + << boost::errinfo_errno(errno)); +#else /* _WIN32 */ + Log(LogCritical, "Socket") + << "getnameinfo() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("getnameinfo") + << errinfo_win32_error(WSAGetLastError())); +#endif /* _WIN32 */ + } + + return std::make_pair(host, service); +} + +/** + * Returns a pair describing the local host and service of the socket. + * + * @returns A pair describing the local host and service. + */ +std::pair<String, String> Socket::GetClientAddressDetails() +{ + std::unique_lock<std::mutex> lock(m_SocketMutex); + + sockaddr_storage sin; + socklen_t len = sizeof(sin); + + if (getsockname(GetFD(), (sockaddr *)&sin, &len) < 0) { +#ifndef _WIN32 + Log(LogCritical, "Socket") + << "getsockname() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("getsockname") + << boost::errinfo_errno(errno)); +#else /* _WIN32 */ + Log(LogCritical, "Socket") + << "getsockname() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("getsockname") + << errinfo_win32_error(WSAGetLastError())); +#endif /* _WIN32 */ + } + + std::pair<String, String> details; + try { + details = GetDetailsFromSockaddr((sockaddr *)&sin, len); + } catch (const std::exception&) { + /* already logged */ + } + + return details; +} + +/** + * Returns a String describing the local address of the socket. + * + * @returns A String describing the local address. + */ +String Socket::GetClientAddress() +{ + return GetHumanReadableAddress(GetClientAddressDetails()); +} + +/** + * Returns a pair describing the peer host and service of the socket. + * + * @returns A pair describing the peer host and service. + */ +std::pair<String, String> Socket::GetPeerAddressDetails() +{ + std::unique_lock<std::mutex> lock(m_SocketMutex); + + sockaddr_storage sin; + socklen_t len = sizeof(sin); + + if (getpeername(GetFD(), (sockaddr *)&sin, &len) < 0) { +#ifndef _WIN32 + Log(LogCritical, "Socket") + << "getpeername() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("getpeername") + << boost::errinfo_errno(errno)); +#else /* _WIN32 */ + Log(LogCritical, "Socket") + << "getpeername() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("getpeername") + << errinfo_win32_error(WSAGetLastError())); +#endif /* _WIN32 */ + } + + std::pair<String, String> details; + try { + details = GetDetailsFromSockaddr((sockaddr *)&sin, len); + } catch (const std::exception&) { + /* already logged */ + } + + return details; +} + +/** + * Returns a String describing the peer address of the socket. + * + * @returns A String describing the peer address. + */ +String Socket::GetPeerAddress() +{ + return GetHumanReadableAddress(GetPeerAddressDetails()); +} + +/** + * Starts listening for incoming client connections. + */ +void Socket::Listen() +{ + if (listen(GetFD(), SOMAXCONN) < 0) { +#ifndef _WIN32 + Log(LogCritical, "Socket") + << "listen() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("listen") + << boost::errinfo_errno(errno)); +#else /* _WIN32 */ + Log(LogCritical, "Socket") + << "listen() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("listen") + << errinfo_win32_error(WSAGetLastError())); +#endif /* _WIN32 */ + } +} + +/** + * Sends data for the socket. + */ +size_t Socket::Write(const void *buffer, size_t count) +{ + int rc; + +#ifndef _WIN32 + rc = write(GetFD(), (const char *)buffer, count); +#else /* _WIN32 */ + rc = send(GetFD(), (const char *)buffer, count, 0); +#endif /* _WIN32 */ + + if (rc < 0) { +#ifndef _WIN32 + Log(LogCritical, "Socket") + << "send() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("send") + << boost::errinfo_errno(errno)); +#else /* _WIN32 */ + Log(LogCritical, "Socket") + << "send() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("send") + << errinfo_win32_error(WSAGetLastError())); +#endif /* _WIN32 */ + } + + return rc; +} + +/** + * Processes data that can be written for this socket. + */ +size_t Socket::Read(void *buffer, size_t count) +{ + int rc; + +#ifndef _WIN32 + rc = read(GetFD(), (char *)buffer, count); +#else /* _WIN32 */ + rc = recv(GetFD(), (char *)buffer, count, 0); +#endif /* _WIN32 */ + + if (rc < 0) { +#ifndef _WIN32 + Log(LogCritical, "Socket") + << "recv() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("recv") + << boost::errinfo_errno(errno)); +#else /* _WIN32 */ + Log(LogCritical, "Socket") + << "recv() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("recv") + << errinfo_win32_error(WSAGetLastError())); +#endif /* _WIN32 */ + } + + return rc; +} + +/** + * Accepts a new client and creates a new client object for it. + */ +Socket::Ptr Socket::Accept() +{ + sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + + SOCKET fd = accept(GetFD(), (sockaddr *)&addr, &addrlen); + +#ifndef _WIN32 + if (fd < 0) { + Log(LogCritical, "Socket") + << "accept() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("accept") + << boost::errinfo_errno(errno)); + } +#else /* _WIN32 */ + if (fd == INVALID_SOCKET) { + Log(LogCritical, "Socket") + << "accept() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("accept") + << errinfo_win32_error(WSAGetLastError())); + } +#endif /* _WIN32 */ + + return new Socket(fd); +} + +bool Socket::Poll(bool read, bool write, struct timeval *timeout) +{ + int rc; + +#ifdef _WIN32 + fd_set readfds, writefds, exceptfds; + + FD_ZERO(&readfds); + if (read) + FD_SET(GetFD(), &readfds); + + FD_ZERO(&writefds); + if (write) + FD_SET(GetFD(), &writefds); + + FD_ZERO(&exceptfds); + FD_SET(GetFD(), &exceptfds); + + rc = select(GetFD() + 1, &readfds, &writefds, &exceptfds, timeout); + + if (rc < 0) { + Log(LogCritical, "Socket") + << "select() failed with error code " << WSAGetLastError() << ", \"" << Utility::FormatErrorNumber(WSAGetLastError()) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("select") + << errinfo_win32_error(WSAGetLastError())); + } +#else /* _WIN32 */ + pollfd pfd; + pfd.fd = GetFD(); + pfd.events = (read ? POLLIN : 0) | (write ? POLLOUT : 0); + pfd.revents = 0; + + rc = poll(&pfd, 1, timeout ? (timeout->tv_sec + 1000 + timeout->tv_usec / 1000) : -1); + + if (rc < 0) { + Log(LogCritical, "Socket") + << "poll() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("poll") + << boost::errinfo_errno(errno)); + } +#endif /* _WIN32 */ + + return (rc != 0); +} + +void Socket::MakeNonBlocking() +{ +#ifdef _WIN32 + Utility::SetNonBlockingSocket(GetFD()); +#else /* _WIN32 */ + Utility::SetNonBlocking(GetFD()); +#endif /* _WIN32 */ +} + +void Socket::SocketPair(SOCKET s[2]) +{ + if (dumb_socketpair(s, 0) < 0) + BOOST_THROW_EXCEPTION(socket_error() + << boost::errinfo_api_function("socketpair") + << boost::errinfo_errno(errno)); +} |