| 1 | /************************************************************************* |
| 2 | * libjson-rpc-cpp |
| 3 | ************************************************************************* |
| 4 | * @file linuxtcpsocketclient.cpp |
| 5 | * @date 17.10.2016 |
| 6 | * @author Alexandre Poirot <alexandre.poirot@legrand.fr> |
| 7 | * @license See attached LICENSE.txt |
| 8 | ************************************************************************/ |
| 9 | |
| 10 | #include "linuxtcpsocketclient.h" |
| 11 | #include "../../common/sharedconstants.h" |
| 12 | #include "../../common/streamreader.h" |
| 13 | #include "../../common/streamwriter.h" |
| 14 | #include <arpa/inet.h> |
| 15 | #include <cstdio> |
| 16 | #include <cstdlib> |
| 17 | #include <cstring> |
| 18 | #include <errno.h> |
| 19 | #include <iostream> |
| 20 | #include <netdb.h> |
| 21 | #include <netinet/in.h> |
| 22 | #include <string.h> |
| 23 | #include <sys/socket.h> |
| 24 | #include <unistd.h> |
| 25 | |
| 26 | using namespace jsonrpc; |
| 27 | using namespace std; |
| 28 | |
| 29 | LinuxTcpSocketClient::LinuxTcpSocketClient(const std::string &hostToConnect, const unsigned int &port) : hostToConnect(hostToConnect), port(port) {} |
| 30 | |
| 31 | LinuxTcpSocketClient::~LinuxTcpSocketClient() {} |
| 32 | |
| 33 | void LinuxTcpSocketClient::SendRPCMessage(const std::string &message, std::string &result) { |
| 34 | int socket_fd = this->Connect(); |
| 35 | |
| 36 | StreamWriter writer; |
| 37 | string toSend = message + DEFAULT_DELIMITER_CHAR; |
| 38 | if (!writer.Write(toSend, socket_fd)) { |
| 39 | throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, "Could not write request" ); |
| 40 | } |
| 41 | |
| 42 | StreamReader reader(DEFAULT_BUFFER_SIZE); |
| 43 | if (!reader.Read(result, socket_fd, DEFAULT_DELIMITER_CHAR)) { |
| 44 | throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, "Could not read response" ); |
| 45 | } |
| 46 | close(socket_fd); |
| 47 | } |
| 48 | |
| 49 | int LinuxTcpSocketClient::Connect() { |
| 50 | if (this->IsIpv4Address(this->hostToConnect)) { |
| 51 | return this->Connect(this->hostToConnect, this->port); |
| 52 | } else // We were given a hostname |
| 53 | { |
| 54 | struct addrinfo *result = NULL; |
| 55 | struct addrinfo hints; |
| 56 | memset(&hints, 0, sizeof(struct addrinfo)); |
| 57 | hints.ai_family = AF_INET; |
| 58 | hints.ai_socktype = SOCK_STREAM; |
| 59 | hints.ai_protocol = IPPROTO_TCP; |
| 60 | char port[6]; |
| 61 | snprintf(port, 6, "%d" , this->port); |
| 62 | int retval = getaddrinfo(this->hostToConnect.c_str(), port, &hints, &result); |
| 63 | if (retval != 0) |
| 64 | throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, "Could not resolve hostname." ); |
| 65 | bool foundValidIp = false; |
| 66 | int socket_fd; |
| 67 | for (struct addrinfo *temp = result; (temp != NULL) && !foundValidIp; temp = temp->ai_next) { |
| 68 | if (temp->ai_family == AF_INET) { |
| 69 | try { |
| 70 | sockaddr_in *sock = reinterpret_cast<sockaddr_in *>(temp->ai_addr); |
| 71 | socket_fd = this->Connect(inet_ntoa(sock->sin_addr), ntohs(sock->sin_port)); |
| 72 | foundValidIp = true; |
| 73 | } catch (const JsonRpcException &e) { |
| 74 | foundValidIp = false; |
| 75 | socket_fd = -1; |
| 76 | } catch (void *p) { |
| 77 | foundValidIp = false; |
| 78 | socket_fd = -1; |
| 79 | } |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | if (!foundValidIp) |
| 84 | throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, "Hostname resolved but connection was refused on the given port." ); |
| 85 | |
| 86 | return socket_fd; |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | int LinuxTcpSocketClient::Connect(const string &ip, const int &port) { |
| 91 | sockaddr_in address; |
| 92 | int socket_fd; |
| 93 | socket_fd = socket(AF_INET, SOCK_STREAM, 0); |
| 94 | if (socket_fd < 0) { |
| 95 | string message = "socket() failed" ; |
| 96 | int err = errno; |
| 97 | switch (err) { |
| 98 | case EACCES: |
| 99 | case EAFNOSUPPORT: |
| 100 | case EINVAL: |
| 101 | case EMFILE: |
| 102 | case ENOBUFS: |
| 103 | case ENOMEM: |
| 104 | case EPROTONOSUPPORT: |
| 105 | message = strerror(err); |
| 106 | break; |
| 107 | } |
| 108 | throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, message); |
| 109 | } |
| 110 | memset(&address, 0, sizeof(sockaddr_in)); |
| 111 | |
| 112 | address.sin_family = AF_INET; |
| 113 | inet_aton(ip.c_str(), &(address.sin_addr)); |
| 114 | address.sin_port = htons(port); |
| 115 | |
| 116 | if (connect(socket_fd, (struct sockaddr *)&address, sizeof(sockaddr_in)) != 0) { |
| 117 | string message = "connect() failed" ; |
| 118 | int err = errno; |
| 119 | switch (err) { |
| 120 | case EACCES: |
| 121 | case EPERM: |
| 122 | case EADDRINUSE: |
| 123 | case EAFNOSUPPORT: |
| 124 | case EAGAIN: |
| 125 | case EALREADY: |
| 126 | case EBADF: |
| 127 | case ECONNREFUSED: |
| 128 | case EFAULT: |
| 129 | case EINPROGRESS: |
| 130 | case EINTR: |
| 131 | case EISCONN: |
| 132 | case ENETUNREACH: |
| 133 | case ENOTSOCK: |
| 134 | case ETIMEDOUT: |
| 135 | message = strerror(err); |
| 136 | break; |
| 137 | } |
| 138 | close(socket_fd); |
| 139 | throw JsonRpcException(Errors::ERROR_CLIENT_CONNECTOR, message); |
| 140 | } |
| 141 | return socket_fd; |
| 142 | } |
| 143 | |
| 144 | bool LinuxTcpSocketClient::IsIpv4Address(const std::string &ip) { |
| 145 | struct in_addr addr; |
| 146 | return (inet_aton(ip.c_str(), &addr) != 0); |
| 147 | } |
| 148 | |