| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one or more | |
| * contributor license agreements. See the NOTICE file distributed with | |
| * this work for additional information regarding copyright ownership. | |
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |
| * (the "License"); you may not use this file except in compliance with | |
| * the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "bsdsocket.h" | |
| #include <log4cxx/helpers/bytebuffer.h> | |
| #include <log4cxx/private/socket_priv.h> | |
| #include <log4cxx/helpers/exception.h> | |
| #include <log4cxx/helpers/loglog.h> | |
| #include <log4cxx/helpers/transcoder.h> | |
| #include <string> | |
| #include <memory> | |
| #ifdef WIN32 | |
| #undef UNICODE | |
| #include <winsock2.h> | |
| #include <ws2tcpip.h> | |
| #pragma comment(lib, "Ws2_32.lib") | |
| #else | |
| #include <sys/types.h> | |
| #include <sys/socket.h> | |
| #include <netinet/in.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/time.h> | |
| #include <net/if.h> | |
| #include <netdb.h> | |
| #include <fcntl.h> | |
| #include <cstring> | |
| #include <unistd.h> | |
| #endif | |
| #include <sstream> | |
| #undef min | |
| #include <algorithm> | |
| // Types | |
| using HandleType = int; | |
| using InfoType = struct addrinfo; | |
| using SockAddrType = struct sockaddr_storage; | |
| #ifdef WIN32 | |
| using SockSizeType = int; | |
| #else | |
| using SockSizeType = unsigned int; | |
| #endif | |
| using SockAddrPtr = std::unique_ptr<SockAddrType>; | |
| /// Streamable array elements | |
| template <typename T, typename S, typename D = T> | |
| class SeparatedArray | |
| { | |
| const T *m_vec; | |
| size_t m_len; | |
| S m_separator; | |
| size_t m_perLine; | |
| public: | |
| SeparatedArray(const T *vec, size_t len, S separ, size_t perLine = 10) | |
| : m_vec(vec) | |
| , m_len(len) | |
| , m_separator(separ) | |
| , m_perLine(perLine) | |
| {} | |
| void Write(std::ostream& os) const | |
| { | |
| if (0 < m_perLine && m_perLine <= m_len) | |
| os << std::endl; | |
| for (size_t i = 0; i < m_len; ++i) | |
| { | |
| if (0 < i) | |
| { | |
| if (0 < m_perLine && 0 == (i % m_perLine)) | |
| os << std::endl; | |
| else | |
| os << m_separator; | |
| } | |
| os << (D)m_vec[i]; | |
| } | |
| } | |
| }; | |
| /// Put \c S separated type \c D elements of \c v onto \c os | |
| template <typename T, typename S, typename D> | |
| std::ostream& | |
| operator<<(std::ostream& os, const SeparatedArray<T, S, D>& v) | |
| { v.Write(os); return os; } | |
| /// An overload that puts the internet address in \c data onto \c os | |
| inline std::ostream& | |
| operator<<(std::ostream& os, const struct sockaddr& data) | |
| { | |
| if (AF_INET == data.sa_family) | |
| { | |
| using Formatter = SeparatedArray<unsigned char, unsigned char, unsigned>; | |
| const struct sockaddr_in& ipv4Data = reinterpret_cast<const struct sockaddr_in&>(data); | |
| const unsigned char* octet = (const unsigned char*)&ipv4Data.sin_addr; | |
| os << std::dec << Formatter(octet, 4, '.') << ':' << ntohs(ipv4Data.sin_port); | |
| } | |
| else | |
| { | |
| using Formatter = SeparatedArray<unsigned short, unsigned char, unsigned>; | |
| const struct sockaddr_in6& ipv6Data = reinterpret_cast<const struct sockaddr_in6&>(data); | |
| const unsigned short* hextet = (const unsigned short*)&ipv6Data.sin6_addr; | |
| os << '[' << std::hex << Formatter(hextet, 8, ':') << ']'; | |
| os << ':' << std::dec << ntohs(ipv6Data.sin6_port); | |
| } | |
| return os; | |
| } | |
| /// An overload that puts the internet address in \c data onto \c os | |
| inline std::ostream& | |
| operator<<(std::ostream& os, const struct sockaddr_storage& data) | |
| { | |
| os << reinterpret_cast<const struct sockaddr&>(data); | |
| return os; | |
| } | |
| namespace | |
| { | |
| class UnexpectedSystemError : public LOG4CXX_NS::helpers::SocketException | |
| { | |
| using BaseType = LOG4CXX_NS::helpers::SocketException; | |
| public: // ...stuctors | |
| UnexpectedSystemError(const char* context, const std::string& data = {}) | |
| #ifdef WIN32 | |
| : BaseType(MakeMessage(::GetLastError(), context, data)) | |
| #else | |
| : BaseType(MakeMessage(errno, context, data)) | |
| #endif | |
| { } | |
| UnexpectedSystemError(int id, const char* context, const std::string& data = {}) | |
| : BaseType(MakeMessage(id, context, data)) | |
| { } | |
| public: // Class methods | |
| static LOG4CXX_NS::LogString MakeConnectMessage() | |
| { | |
| #ifdef WIN32 | |
| return MakeMessage(WSAGetLastError(), "connect"); | |
| #else | |
| return MakeMessage(errno, "connect"); | |
| #endif | |
| } | |
| static LOG4CXX_NS::LogString MakeMessage(int id, const char* context, const std::string& data = {}) | |
| { | |
| std::stringstream ss; | |
| ss << "System error 0x" << std::hex << id << ": " << context; | |
| if (!data.empty()) | |
| ss << ' ' << data; | |
| LOG4CXX_DECODE_CHAR(result, ss.str()); | |
| return result; | |
| } | |
| }; | |
| /// An adaptor of message data for printing | |
| class MessageData | |
| { | |
| const unsigned char *m_vec; | |
| size_t m_len; | |
| public: | |
| /// A streamable version of the \c len bytes in \c vec | |
| MessageData(const void *vec, size_t len) | |
| : m_vec(reinterpret_cast<const unsigned char *>(vec)) | |
| , m_len(len) | |
| {} | |
| void Write(std::ostream& os) const | |
| { | |
| for (size_t i = 0; i < m_len; ++i) | |
| { | |
| if ('&' == m_vec[i]) | |
| os << "&"; | |
| else if (isprint(m_vec[i])) | |
| os << m_vec[i]; | |
| else | |
| os << "&#" << std::hex << int(m_vec[i]) << ';'; | |
| } | |
| } | |
| }; | |
| std::ostream& | |
| operator<<(std::ostream& s, const MessageData& v) | |
| { v.Write(s); return s; } | |
| #ifdef WIN32 | |
| class SystemInitialiser | |
| { | |
| public: | |
| SystemInitialiser() | |
| { | |
| WORD version = MAKEWORD(2, 2); | |
| WSADATA data; | |
| if (WSAStartup(version, &data) != 0) | |
| { | |
| throw UnexpectedSystemError(WSAGetLastError(), "initialising WSA"); | |
| } | |
| } | |
| }; | |
| void CheckWSA() | |
| { | |
| static SystemInitialiser _; | |
| } | |
| #endif | |
| } // namespace | |
| namespace LOG4CXX_NS::helpers | |
| { | |
| struct BSDSocket::Data : public Socket::SocketPrivate | |
| { | |
| int m_inetType; //!< SOCK_DGRAM or SOCK_STREAM | |
| int m_inetFamily; //!< AF_INET or AF_INET6 | |
| InfoType* m_remote{ 0 }; //!< A chain of interfaces on which data may be sent and received | |
| HandleType m_handle{ -1 }; //!< The handle for send and receive | |
| SockAddrPtr m_remoteAddress{ std::make_unique<SockAddrType>() }; | |
| SockSizeType m_remoteAddressSize{ 0 }; | |
| LoggerPtr m_log{ Logger::getLogger("BSDSocket") }; | |
| Data(bool isDatagramType = false, bool ipV6 = false) | |
| : m_inetType{ isDatagramType ? SOCK_DGRAM : SOCK_STREAM } | |
| , m_inetFamily{ ipV6 ? AF_INET6 : AF_INET } | |
| { | |
| #ifdef WIN32 | |
| CheckWSA(); | |
| #endif | |
| } | |
| Data(const InetAddressPtr& address, int port, bool isDatagramType = false, bool ipV6 = false) | |
| : Socket::SocketPrivate(address, port) | |
| , m_inetType{ isDatagramType ? SOCK_DGRAM : SOCK_STREAM } | |
| , m_inetFamily{ ipV6 ? AF_INET6 : AF_INET } | |
| { | |
| #ifdef WIN32 | |
| CheckWSA(); | |
| #endif | |
| } | |
| /// Prepare the socket for use. | |
| void open(); | |
| /// Release resources required by an open socket. | |
| void close(); | |
| /// Send the bytes in \c data. | |
| size_t write(ByteBuffer& data); | |
| /// Initialise the port field of \c addr to \c port | |
| void setInternetPort(struct sockaddr* addr, int port); | |
| /// Set up \c m_remote for a \c m_inetType connection on \c port to \c node using \c m_inetFamily | |
| void setRemote(const InetAddressPtr& address, int port); | |
| /// Initialise \c m_remoteAddress from \c data | |
| void setRemoteAddress(const struct sockaddr* data, size_t dataSize); | |
| }; | |
| // Prepare the channel for use. | |
| void | |
| BSDSocket::Data::open() | |
| { | |
| LOGLOG_DEBUG(m_log, "open:"); | |
| setRemote(this->address, this->port); | |
| m_handle = (HandleType)socket(m_inetFamily, m_inetType, SOCK_DGRAM == m_inetType ? IPPROTO_UDP : IPPROTO_TCP); | |
| if (m_handle < 0) | |
| throw UnexpectedSystemError("creating socket"); | |
| #ifdef WIN32 | |
| // Prevent a WSAECONNRESET when a previous send operation reported a ICMP Port Unreachable message | |
| // to allow sending when the destination port is not yet receiving data | |
| static const int SIO_UDP_CONNRESET = _WSAIOW(IOC_VENDOR, 12); | |
| BOOL bNewBehavior = FALSE; | |
| DWORD dwBytesReturned = 0; | |
| WSAIoctl | |
| ( m_handle | |
| , SIO_UDP_CONNRESET // dwIoControlCode | |
| , &bNewBehavior // lpvInBuffer | |
| , sizeof (bNewBehavior) // cbInBuffer | |
| , NULL // lpvOutBuffer | |
| , 0 // cbOutBuffer | |
| , &dwBytesReturned // lpcbBytesReturned | |
| , NULL // lpOverlapped | |
| , NULL // lpCompletionRoutine | |
| ); | |
| #endif | |
| LOGLOG_DEBUG(m_log, "open:" | |
| << " connect " << m_handle | |
| << " to address " << *m_remoteAddress | |
| ); | |
| auto rs = connect(m_handle, (const struct sockaddr *)m_remoteAddress.get(), m_remoteAddressSize); | |
| if (rs < 0) | |
| throw ConnectException(UnexpectedSystemError::MakeConnectMessage()); | |
| LOGLOG_DEBUG(m_log, "open:" << " handle " << m_handle); | |
| } | |
| // Release resources required by an open channel. | |
| void | |
| BSDSocket::Data::close() | |
| { | |
| LOGLOG_DEBUG(m_log, "close:" << " handle " << m_handle); | |
| #ifdef WIN32 | |
| closesocket(m_handle); | |
| #else | |
| ::close(m_handle); | |
| #endif | |
| m_handle = -1; | |
| } | |
| // Send the \c size bytes at \c data. | |
| size_t | |
| BSDSocket::Data::write(ByteBuffer& data) | |
| { | |
| auto pMessage = data.current(); | |
| auto byteCount = static_cast<int>(data.remaining()); | |
| LOGLOG_TRACE(m_log, "write:" | |
| << " byteCount " << byteCount | |
| << " to " << *m_remoteAddress | |
| << "\n" << MessageData(pMessage, byteCount) | |
| ); | |
| int rs = sendto | |
| ( m_handle | |
| , pMessage | |
| , byteCount | |
| , 0 | |
| , reinterpret_cast<struct sockaddr*>(m_remoteAddress.get()) | |
| , (int)m_remoteAddressSize | |
| ); | |
| if (rs < 0) | |
| throw UnexpectedSystemError("send"); | |
| data.increment_position(byteCount); | |
| return byteCount; | |
| } | |
| // Set up \c m_remote for a \c m_inetType connection on \c port to \c address | |
| void | |
| BSDSocket::Data::setRemote(const InetAddressPtr& address, int port) | |
| { | |
| LOG4CXX_ENCODE_CHAR(hostAddress, address->getHostAddress()); | |
| LOGLOG_DEBUG(m_log, "setRemote:" | |
| << " hostAddress " << hostAddress | |
| << " port " << port | |
| << " inetType " << m_inetType | |
| << " ipVersion " << m_inetFamily | |
| ); | |
| struct addrinfo hints; | |
| memset(&hints, 0, sizeof (hints)); | |
| hints.ai_family = m_inetFamily; | |
| hints.ai_socktype = m_inetType; | |
| auto service = std::to_string(port); | |
| int rs = getaddrinfo(hostAddress.c_str(), service.c_str(), &hints, &m_remote); | |
| #ifdef WIN32 | |
| if (0 != rs) | |
| throw UnexpectedSystemError(rs, "getaddrinfo"); | |
| #else | |
| if (0 == rs) | |
| ; | |
| else if (EAI_SYSTEM == rs) | |
| throw UnexpectedSystemError("getaddrinfo"); | |
| else | |
| throw UnexpectedSystemError(rs, gai_strerror(rs), "getaddrinfo"); | |
| #endif | |
| setRemoteAddress(m_remote->ai_addr, m_remote->ai_addrlen); | |
| setInternetPort(m_remote->ai_addr, port); | |
| } | |
| // Initialise \c m_remoteAddress from \c data | |
| void | |
| BSDSocket::Data::setRemoteAddress(const struct sockaddr* data, size_t dataSize) | |
| { | |
| LOGLOG_DEBUG(m_log, "setRemoteAddress:" << " size " << dataSize << " address " << *data); | |
| m_remoteAddressSize = SockSizeType(std::min(dataSize, sizeof (SockAddrType))); | |
| memcpy(m_remoteAddress.get(), data, m_remoteAddressSize); | |
| } | |
| // Initialise the port field of \c addr to \c port | |
| void | |
| BSDSocket::Data::setInternetPort(struct sockaddr* addr, int port) | |
| { | |
| LOGLOG_DEBUG(m_log, "setInternetPort: " << port); | |
| if (AF_INET == addr->sa_family) | |
| { | |
| struct sockaddr_in* ipv4Data = reinterpret_cast<struct sockaddr_in*>(addr); | |
| ipv4Data->sin_port = htons(port); | |
| } | |
| else | |
| { | |
| struct sockaddr_in6* ipv6Data = reinterpret_cast<struct sockaddr_in6*>(addr); | |
| ipv6Data->sin6_port = htons(port); | |
| } | |
| } | |
| IMPLEMENT_LOG4CXX_OBJECT(BSDSocket) | |
| const int | |
| BSDSocket::DefaultPort{ 4560 }; | |
| #define _priv static_cast<Data*>(m_priv.get()) | |
| // A \c isDatagramType connection | |
| BSDSocket::BSDSocket(bool isDatagramType, bool ipV6) | |
| : Socket{ std::make_unique<Data>(isDatagramType, ipV6) } | |
| { | |
| } | |
| // A \c inetType connection on \c port to \c address (or as a server if 0) | |
| BSDSocket::BSDSocket(const InetAddressPtr& address, int port, bool isDatagramType, bool ipV6) | |
| : Socket{ std::make_unique<Data>(address, port, isDatagramType, ipV6) } | |
| { | |
| } | |
| BSDSocket::~BSDSocket() | |
| { | |
| } | |
| void | |
| BSDSocket::setNonBlocking(bool newValue) | |
| { | |
| #ifdef WIN32 | |
| u_long ulValue = newValue; | |
| if (ioctlsocket(_priv->m_handle, FIONBIO, &ulValue) == SOCKET_ERROR) | |
| throw UnexpectedSystemError(WSAGetLastError(), "ioctlsocket" ); | |
| #else | |
| int fd_flags = fcntl(_priv->m_handle, F_GETFL, 0); | |
| #if defined(O_NONBLOCK) | |
| fd_flags |= O_NONBLOCK; | |
| #elif defined(O_NDELAY) | |
| fd_flags |= O_NDELAY; | |
| #elif defined(FNDELAY) | |
| fd_flags |= FNDELAY; | |
| #else | |
| #error Making sockets non-blocking not supported on your platform. | |
| #endif | |
| if (fcntl(_priv->m_handle, F_SETFL, fd_flags) == -1) | |
| throw UnexpectedSystemError("fcntl"); | |
| #endif | |
| } | |
| // Prepare the channel for use. | |
| void | |
| BSDSocket::open() | |
| { | |
| _priv->open(); | |
| } | |
| // Is the port available for use? | |
| bool | |
| BSDSocket::is_open() | |
| { | |
| return 0 <= _priv->m_handle; | |
| } | |
| // Release resources required by an open channel. | |
| void | |
| BSDSocket::close() | |
| { | |
| _priv->close(); | |
| } | |
| // Send the \c size bytes at \c data. | |
| size_t | |
| BSDSocket::write(ByteBuffer& data) | |
| { | |
| return _priv->write(data); | |
| } | |
| } // namespace LOG4CXX_NS::helpers |