Kinetic C/C++ Client
 All Classes Functions Variables Pages
socket_wrapper.cc
1 /*
2  * kinetic-cpp-client
3  * Copyright (C) 2014 Seagate Technology.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  */
20 
21 #include <fcntl.h>
22 #include <sys/types.h>
23 #include <sys/socket.h>
24 #include <netinet/in.h>
25 #include <arpa/inet.h>
26 #include <netdb.h>
27 #include <unistd.h>
28 #include <string>
29 #include <exception>
30 #include <stdexcept>
31 #include "glog/logging.h"
32 #include "socket_wrapper.h"
33 
34 namespace kinetic {
35 
36 using std::string;
37 
38 SocketWrapper::SocketWrapper(const std::string& host, int port, bool use_ssl, bool nonblocking)
39  : ctx_(nullptr), ssl_(nullptr), host_(host), port_(port), nonblocking_(nonblocking), fd_(-1) {
40  if(!use_ssl) return;
41 
42  SSL_library_init();
43  OpenSSL_add_all_algorithms();
44  ctx_ = SSL_CTX_new(SSLv23_client_method());
45  ssl_ = SSL_new(ctx_);
46  if(!ssl_ || !ctx_)
47  throw std::runtime_error("Failed Setting up SSL environment.");
48  SSL_set_mode(ssl_, SSL_MODE_AUTO_RETRY);
49 }
50 
51 SocketWrapper::~SocketWrapper() {
52  if (fd_ == -1) {
53  LOG(INFO) << "Not connected so no cleanup needed";
54  } else {
55  LOG(INFO) << "Closing socket with fd " << fd_;
56  if (close(fd_)) {
57  PLOG(ERROR) << "Error closing socket fd " << fd_;
58  }
59  }
60  if(ssl_) SSL_free(ssl_);
61  if(ctx_) SSL_CTX_free(ctx_);
62 }
63 
65  LOG(INFO) << "Connecting to " << host_ << ":" << port_;
66 
67  struct addrinfo hints;
68  memset(&hints, 0, sizeof(struct addrinfo));
69 
70  // could be inet or inet6
71  hints.ai_family = PF_UNSPEC;
72  hints.ai_socktype = SOCK_STREAM;
73  hints.ai_protocol = IPPROTO_TCP;
74  hints.ai_flags = AI_NUMERICSERV;
75 
76  struct addrinfo* result;
77 
78  string port_str = std::to_string(port_);
79 
80  if (int res = getaddrinfo(host_.c_str(), port_str.c_str(), &hints, &result) != 0) {
81  LOG(ERROR) << "Could not resolve host " << host_ << " port " << port_ << ": "
82  << gai_strerror(res);
83  return false;
84  }
85 
86  struct addrinfo* ai;
87  int socket_fd;
88  for (ai = result; ai != NULL; ai = ai->ai_next) {
89  char host[NI_MAXHOST];
90  char service[NI_MAXSERV];
91  if (int res = getnameinfo(ai->ai_addr, ai->ai_addrlen, host, sizeof(host), service,
92  sizeof(service), NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
93  LOG(ERROR) << "Could not get name info: " << gai_strerror(res);
94  continue;
95  } else {
96  LOG(INFO) << "Trying to connect to " << string(host) << " on " << string(service);
97  }
98 
99  socket_fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
100 
101  if (socket_fd == -1) {
102  LOG(WARNING) << "Could not create socket";
103  continue;
104  }
105 
106  // os x won't let us set close-on-exec when creating the socket, so set it separately
107  int current_fd_flags = fcntl(socket_fd, F_GETFD);
108  if (current_fd_flags == -1) {
109  PLOG(ERROR) << "Failed to get socket fd flags";
110  close(socket_fd);
111  continue;
112  }
113  if (fcntl(socket_fd, F_SETFD, current_fd_flags | FD_CLOEXEC) == -1) {
114  PLOG(ERROR) << "Failed to set socket close-on-exit";
115  close(socket_fd);
116  continue;
117  }
118 
119  // On BSD-like systems we can set SO_NOSIGPIPE on the socket to prevent it from sending a
120  // PIPE signal and bringing down the whole application if the server closes the socket
121  // forcibly
122 #ifdef SO_NOSIGPIPE
123  int set = 1;
124  int setsockopt_result = setsockopt(socket_fd, SOL_SOCKET, SO_NOSIGPIPE, &set, sizeof(set));
125  // Allow ENOTSOCK because it allows tests to use pipes instead of real sockets
126  if (setsockopt_result != 0 && setsockopt_result != ENOTSOCK) {
127  PLOG(ERROR) << "Failed to set SO_NOSIGPIPE on socket";
128  close(socket_fd);
129  continue;
130  }
131 #endif
132 
133  if (connect(socket_fd, ai->ai_addr, ai->ai_addrlen) == -1) {
134  PLOG(WARNING) << "Unable to connect";
135  close(socket_fd);
136  continue;
137  }
138 
139  if (nonblocking_ && fcntl(socket_fd, F_SETFL, O_NONBLOCK) != 0) {
140  PLOG(ERROR) << "Failed to set socket nonblocking";
141  close(socket_fd);
142  continue;
143  }
144 
145  break;
146  }
147 
148  freeaddrinfo(result);
149 
150  if (ai == NULL) {
151  // we went through all addresses without finding one we could bind to
152  LOG(ERROR) << "Could not connect to " << host_ << " on port " << port_;
153  return false;
154  }
155 
156  fd_ = socket_fd;
157  if(ssl_) return ConnectSSL();
158  return true;
159 }
160 
161 #include <openssl/err.h>
162 
163 bool SocketWrapper::ConnectSSL()
164 {
165  SSL_set_fd(ssl_,fd_);
166  int rtn = SSL_connect(ssl_);
167  if(rtn == 1)
168  return true;
169 
170  int err = SSL_get_error(ssl_, rtn);
171  if( err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE ){
172  fd_set read_fds, write_fds;
173  FD_ZERO(&read_fds); FD_ZERO(&write_fds);
174  if(err == SSL_ERROR_WANT_READ) FD_SET(fd_, &read_fds);
175  if(err == SSL_ERROR_WANT_WRITE) FD_SET(fd_, &write_fds);
176  struct timeval tv = {1,1};
177  select(fd_+1, &read_fds, &write_fds, NULL, &tv);
178  return ConnectSSL();
179  }
180  return false;
181 }
182 
184  return ssl_;
185 }
186 
188  return fd_;
189 }
190 
191 } // namespace kinetic
SSL * getSSL()
Returns nullptr if SSL hasn't been initialized.
bool Connect()
Actually open the connection to the socket. The details of the host/port/protocol to connect on depen...
int fd()
Returns the FD.