Branch data Line data Source code
1 : : // Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
2 : : //
3 : : // Permission to use, copy, modify, and/or distribute this software for any
4 : : // purpose with or without fee is hereby granted, provided that the above
5 : : // copyright notice and this permission notice appear in all copies.
6 : : //
7 : : // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
8 : : // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9 : : // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
10 : : // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11 : : // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
12 : : // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13 : : // PERFORMANCE OF THIS SOFTWARE.
14 : : #include <config.h>
15 : :
16 : : #include "socket_request.h"
17 : : #include <server_common/logger.h>
18 : :
19 : : #include <config/ccsession.h>
20 : : #include <cc/session.h>
21 : : #include <cc/data.h>
22 : : #include <util/io/fd.h>
23 : : #include <util/io/fd_share.h>
24 : :
25 : : #include <sys/un.h>
26 : : #include <sys/socket.h>
27 : : #include <cerrno>
28 : : #include <csignal>
29 : : #include <cstddef>
30 : :
31 : : namespace isc {
32 : : namespace server_common {
33 : :
34 : : namespace {
35 : : SocketRequestor* requestor(NULL);
36 : :
37 : : // Before the boss process calls send_fd, it first sends this
38 : : // string to indicate success, followed by the file descriptor
39 : : const std::string& CREATOR_SOCKET_OK() {
40 [ + + ][ + - ]: 9 : static const std::string str("1\n");
[ + - ]
41 : : return (str);
42 : : }
43 : :
44 : : // Before the boss process calls send_fd, it sends this
45 : : // string to indicate failure. It will not send a file descriptor.
46 : : const std::string& CREATOR_SOCKET_UNAVAILABLE() {
47 [ + + ][ + - ]: 10 : static const std::string str("0\n");
[ + - ]
48 : : return (str);
49 : : }
50 : :
51 : : // The name of the ccsession command to request a socket from boss
52 : : // (the actual format of command and response are hardcoded in their
53 : : // respective methods)
54 : : const std::string& REQUEST_SOCKET_COMMAND() {
55 [ + + ][ + - ]: 51 : static const std::string str("get_socket");
[ + - ]
56 : : return (str);
57 : : }
58 : :
59 : : // The name of the ccsession command to tell boss we no longer need
60 : : // a socket (the actual format of command and response are hardcoded
61 : : // in their respective methods)
62 : : const std::string& RELEASE_SOCKET_COMMAND() {
63 [ + + ][ + - ]: 12 : static const std::string str("drop_socket");
[ + - ]
64 : : return (str);
65 : : }
66 : :
67 : : // RCode constants for the get_token command
68 : : const size_t SOCKET_ERROR_CODE = 2;
69 : : const size_t SHARE_ERROR_CODE = 3;
70 : :
71 : : // A helper converter from numeric protocol ID to the corresponding string.
72 : : // used both for generating a message for the boss process and for logging.
73 : : inline const char*
74 : : protocolString(SocketRequestor::Protocol protocol) {
75 [ + - - ]: 62 : switch (protocol) {
[ + - + ]
76 : : case SocketRequestor::TCP:
77 : : return ("TCP");
78 : : case SocketRequestor::UDP:
79 : : return ("UDP");
80 : : default:
81 : : return ("unknown protocol");
82 : : }
83 : : }
84 : :
85 : : // Creates the cc session message to request a socket.
86 : : // The actual command format is hardcoded, and should match
87 : : // the format as read in bind10_src.py.in
88 : : isc::data::ConstElementPtr
89 : 57 : createRequestSocketMessage(SocketRequestor::Protocol protocol,
90 : : const std::string& address, uint16_t port,
91 : : SocketRequestor::ShareMode share_mode,
92 : : const std::string& share_name)
93 : : {
94 : 57 : const isc::data::ElementPtr request = isc::data::Element::createMap();
95 [ + - ][ + - ]: 114 : request->set("address", isc::data::Element::create(address));
[ + - ]
96 [ + - ][ + - ]: 114 : request->set("port", isc::data::Element::create(port));
97 [ + + ]: 57 : if (protocol != SocketRequestor::TCP && protocol != SocketRequestor::UDP) {
98 [ + - ][ + - ]: 6 : isc_throw(InvalidParameter, "invalid protocol: " << protocol);
[ + - ]
99 : : }
100 : 54 : request->set("protocol",
101 [ + - ][ + - ]: 108 : isc::data::Element::create(protocolString(protocol)));
[ + - ]
102 [ + + + + ]: 54 : switch (share_mode) {
103 : : case SocketRequestor::DONT_SHARE:
104 [ + - ][ + - ]: 84 : request->set("share_mode", isc::data::Element::create("NO"));
[ + - ]
105 : : break;
106 : : case SocketRequestor::SHARE_SAME:
107 [ + - ][ + - ]: 12 : request->set("share_mode", isc::data::Element::create("SAMEAPP"));
[ + - ]
108 : : break;
109 : : case SocketRequestor::SHARE_ANY:
110 [ + - ][ + - ]: 6 : request->set("share_mode", isc::data::Element::create("ANY"));
[ + - ]
111 : : break;
112 : : default:
113 [ + - ][ + - ]: 6 : isc_throw(InvalidParameter, "invalid share mode: " << share_mode);
[ + - ]
114 : : }
115 [ + - ][ + - ]: 102 : request->set("share_name", isc::data::Element::create(share_name));
[ + - ]
116 : :
117 [ + - ]: 102 : return (isc::config::createCommand(REQUEST_SOCKET_COMMAND(), request));
118 : : }
119 : :
120 : : isc::data::ConstElementPtr
121 : 12 : createReleaseSocketMessage(const std::string& token) {
122 : 12 : const isc::data::ElementPtr release = isc::data::Element::createMap();
123 [ + - ][ + - ]: 24 : release->set("token", isc::data::Element::create(token));
[ + - ]
124 : :
125 [ + - ]: 24 : return (isc::config::createCommand(RELEASE_SOCKET_COMMAND(), release));
126 : : }
127 : :
128 : : // Checks and parses the response receive from Boss
129 : : // If successful, token and path will be set to the values found in the
130 : : // answer.
131 : : // If the response was an error response, or does not contain the
132 : : // expected elements, a CCSessionError is raised.
133 : : void
134 : 36 : readRequestSocketAnswer(isc::data::ConstElementPtr recv_msg,
135 : : std::string& token, std::string& path)
136 : : {
137 : : int rcode;
138 : : isc::data::ConstElementPtr answer = isc::config::parseAnswer(rcode,
139 [ + - ]: 36 : recv_msg);
140 : : // Translate known rcodes to the corresponding exceptions
141 [ + + ]: 36 : if (rcode == SOCKET_ERROR_CODE) {
142 [ + - ][ + - ]: 6 : isc_throw(SocketRequestor::SocketAllocateError, answer->str());
[ + - ]
143 : : }
144 [ + + ]: 33 : if (rcode == SHARE_ERROR_CODE) {
145 [ + - ][ + - ]: 6 : isc_throw(SocketRequestor::ShareError, answer->str());
[ + - ]
146 : : }
147 : : // The unknown exceptions
148 [ + + ]: 30 : if (rcode != 0) {
149 [ + - ][ + - ]: 6 : isc_throw(isc::config::CCSessionError,
[ + - ][ + - ]
150 : : "Error response when requesting socket: " << answer->str());
151 : : }
152 : :
153 [ + + ][ + - ]: 27 : if (!answer || !answer->contains("token") || !answer->contains("path")) {
[ + - ][ + - ]
[ + - ][ + - ]
[ - + ][ + + ]
[ + + ][ + + ]
[ # # ][ # # ]
154 [ + - ][ + - ]: 6 : isc_throw(isc::config::CCSessionError,
155 : : "Malformed answer when requesting socket");
156 : : }
157 [ + - ][ + - ]: 72 : token = answer->get("token")->stringValue();
[ + - ]
158 [ + - ][ + - ]: 72 : path = answer->get("path")->stringValue();
[ + - ]
159 : 24 : }
160 : :
161 : : // Connect to the domain socket that has been received from Boss.
162 : : // (i.e. the one that is used to pass created sockets over).
163 : : //
164 : : // This should only be called if the socket had not been connected to
165 : : // already. To get the socket and reuse existing ones, use
166 : : // getFdShareSocket()
167 : : //
168 : : // \param path The domain socket to connect to
169 : : // \exception SocketError if the socket cannot be connected to
170 : : // \return the socket file descriptor
171 : : int
172 : 15 : createFdShareSocket(const std::string& path) {
173 : : // TODO: Current master has socketsession code and better way
174 : : // of handling errors without potential leaks for this. It is
175 : : // not public there at this moment, but when this is merged
176 : : // we should make a ticket to move this functionality to the
177 : : // SocketSessionReceiver and use that.
178 : 15 : const int sock_pass_fd = socket(AF_UNIX, SOCK_STREAM, 0);
179 [ - + ]: 15 : if (sock_pass_fd == -1) {
180 [ # # ][ # # ]: 0 : isc_throw(SocketRequestor::SocketError,
[ # # ][ # # ]
[ # # ]
181 : : "Unable to open domain socket " << path <<
182 : : ": " << strerror(errno));
183 : : }
184 : : struct sockaddr_un sock_pass_addr;
185 : 15 : sock_pass_addr.sun_family = AF_UNIX;
186 [ + + ]: 15 : if (path.size() >= sizeof(sock_pass_addr.sun_path)) {
187 : 6 : close(sock_pass_fd);
188 [ + - ][ + - ]: 12 : isc_throw(SocketRequestor::SocketError,
[ + - ][ + - ]
189 : : "Unable to open domain socket " << path <<
190 : : ": path too long");
191 : : }
192 : : #ifdef HAVE_SA_LEN
193 : : sock_pass_addr.sun_len = path.size();
194 : : #endif
195 : : strcpy(sock_pass_addr.sun_path, path.c_str());
196 : 9 : const socklen_t len = path.size() + offsetof(struct sockaddr_un, sun_path);
197 : : // Yes, C-style cast bad. See previous comment about SocketSessionReceiver.
198 [ + + ]: 9 : if (connect(sock_pass_fd, (const struct sockaddr*)&sock_pass_addr,
199 : 9 : len) == -1) {
200 : 6 : close(sock_pass_fd);
201 [ + - ][ + - ]: 12 : isc_throw(SocketRequestor::SocketError,
[ + - ][ + - ]
[ + - ]
202 : : "Unable to open domain socket " << path <<
203 : : ": " << strerror(errno));
204 : : }
205 : 3 : return (sock_pass_fd);
206 : : }
207 : :
208 : : // Reads a socket fd over the given socket (using recv_fd()).
209 : : //
210 : : // \exception SocketError if the socket cannot be read
211 : : // \return the socket fd that has been read
212 : : int
213 : 12 : getSocketFd(const std::string& token, int sock_pass_fd) {
214 : : // Tell the boss the socket token.
215 : 12 : const std::string token_data = token + "\n";
216 [ + + ]: 12 : if (!isc::util::io::write_data(sock_pass_fd, token_data.c_str(),
217 [ + - ]: 12 : token_data.size())) {
218 [ + - ][ + - ]: 4 : isc_throw(SocketRequestor::SocketError, "Error writing socket token");
[ + - ]
219 : : }
220 : :
221 : : // Boss first sends some data to signal that getting the socket
222 : : // from its cache succeeded
223 : : char status[3]; // We need a space for trailing \0, hence 3
224 : : memset(status, 0, 3);
225 [ + - ][ - + ]: 10 : if (isc::util::io::read_data(sock_pass_fd, status, 2) < 2) {
226 [ # # ][ # # ]: 0 : isc_throw(SocketRequestor::SocketError,
[ # # ]
227 : : "Error reading status code while requesting socket");
228 : : }
229 : : // Actual status value hardcoded by boss atm.
230 [ + + ]: 10 : if (CREATOR_SOCKET_UNAVAILABLE() == status) {
231 [ + - ][ + - ]: 2 : isc_throw(SocketRequestor::SocketError,
[ + - ]
232 : : "CREATOR_SOCKET_UNAVAILABLE returned");
233 [ - + ]: 9 : } else if (CREATOR_SOCKET_OK() != status) {
234 [ # # ][ # # ]: 0 : isc_throw(SocketRequestor::SocketError,
[ # # ][ # # ]
[ # # ]
235 : : "Unknown status code returned before recv_fd '" << status <<
236 : : "'");
237 : : }
238 : :
239 [ + - ]: 9 : const int passed_sock_fd = isc::util::io::recv_fd(sock_pass_fd);
240 : :
241 : : // check for error values of passed_sock_fd (see fd_share.h)
242 [ + + ]: 9 : if (passed_sock_fd < 0) {
243 [ + - - ]: 1 : switch (passed_sock_fd) {
244 : : case isc::util::io::FD_SYSTEM_ERROR:
245 [ + - ][ + - ]: 2 : isc_throw(SocketRequestor::SocketError,
[ + - ]
246 : : "FD_SYSTEM_ERROR while requesting socket");
247 : : break;
248 : : case isc::util::io::FD_OTHER_ERROR:
249 [ # # ][ # # ]: 0 : isc_throw(SocketRequestor::SocketError,
[ # # ]
250 : : "FD_OTHER_ERROR while requesting socket");
251 : : break;
252 : : default:
253 [ # # ][ # # ]: 0 : isc_throw(SocketRequestor::SocketError,
[ # # ]
254 : : "Unknown error while requesting socket");
255 : : }
256 : : }
257 : 8 : return (passed_sock_fd);
258 : : }
259 : :
260 : : // This implementation class for SocketRequestor uses
261 : : // a CC session for communication with the boss process,
262 : : // and fd_share to read out the socket(s).
263 : : // Since we only use a reference to the session, it must never
264 : : // be closed during the lifetime of this class
265 : : class SocketRequestorCCSession : public SocketRequestor {
266 : : public:
267 : : SocketRequestorCCSession(cc::AbstractSession& session,
268 : : const std::string& app_name) :
269 : : session_(session),
270 [ + - ]: 18 : app_name_(app_name)
271 : : {
272 : : // We need to filter SIGPIPE to prevent it from happening in
273 : : // getSocketFd() while writing to the UNIX domain socket after the
274 : : // remote end closed it. See lib/util/io/socketsession for more
275 : : // background details.
276 : : // Note: we should eventually unify this level of details into a single
277 : : // module. Setting a single filter here should be considered a short
278 : : // term workaround.
279 [ - + ]: 18 : if (std::signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
280 [ # # ][ # # ]: 0 : isc_throw(Unexpected, "Failed to filter SIGPIPE: " <<
[ # # ]
281 : : strerror(errno));
282 : : }
283 [ + - ][ + - ]: 36 : LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, SOCKETREQUESTOR_CREATED).
[ - + ]
284 [ + - ][ + - ]: 18 : arg(app_name);
285 : : }
286 : :
287 : 16 : ~SocketRequestorCCSession() {
288 : : closeFdShareSockets();
289 [ + - ][ + - ]: 16 : LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, SOCKETREQUESTOR_DESTROYED);
[ + - ][ + - ]
290 : 32 : }
291 : :
292 : 57 : virtual SocketID requestSocket(Protocol protocol,
293 : : const std::string& address,
294 : : uint16_t port, ShareMode share_mode,
295 : : const std::string& share_name)
296 : : {
297 : : const isc::data::ConstElementPtr request_msg =
298 : : createRequestSocketMessage(protocol, address, port,
299 : : share_mode,
300 : : share_name.empty() ? app_name_ :
301 [ + + ]: 57 : share_name);
302 : :
303 : : // Send it to boss
304 [ + - ][ + - ]: 153 : const int seq = session_.group_sendmsg(request_msg, "Boss");
[ + - ][ + - ]
305 : :
306 : : // Get the answer from the boss.
307 : : // Just do a blocking read, we can't really do much anyway
308 : : isc::data::ConstElementPtr env, recv_msg;
309 [ + - ][ + + ]: 51 : if (!session_.group_recvmsg(env, recv_msg, false, seq)) {
310 [ + - ][ + - ]: 30 : isc_throw(isc::config::CCSessionError,
311 : : "Incomplete response when requesting socket");
312 : : }
313 : :
314 : : // Read the socket file from the answer
315 : 36 : std::string token, path;
316 [ + + ]: 36 : readRequestSocketAnswer(recv_msg, token, path);
317 : : // get the domain socket over which we will receive the
318 : : // real socket
319 : 12 : const int sock_pass_fd = getFdShareSocket(path);
320 : :
321 : : // and finally get the socket itself
322 [ + + ]: 12 : const int passed_sock_fd = getSocketFd(token, sock_pass_fd);
323 [ + - ][ + - ]: 16 : LOG_DEBUG(logger, DBGLVL_TRACE_DETAIL, SOCKETREQUESTOR_GETSOCKET).
[ + - ]
324 [ + - ][ + - ]: 8 : arg(protocolString(protocol)).arg(address).arg(port).
[ + - ][ + - ]
325 [ + - ][ + - ]: 8 : arg(passed_sock_fd).arg(token).arg(path);
[ + - ]
326 : 8 : return (SocketID(passed_sock_fd, token));
327 : : }
328 : :
329 : 12 : virtual void releaseSocket(const std::string& token) {
330 : : const isc::data::ConstElementPtr release_msg =
331 : 12 : createReleaseSocketMessage(token);
332 : :
333 : : // Send it to boss
334 [ + - ][ + - ]: 36 : const int seq = session_.group_sendmsg(release_msg, "Boss");
[ + - ][ + - ]
335 [ + - ][ + - ]: 24 : LOG_DEBUG(logger, DBGLVL_TRACE_DETAIL, SOCKETREQUESTOR_RELEASESOCKET).
[ + - ]
336 [ + - ][ + - ]: 12 : arg(token);
337 : :
338 : : // Get the answer from the boss.
339 : : // Just do a blocking read, we can't really do much anyway
340 : : isc::data::ConstElementPtr env, recv_msg;
341 [ + - ][ + + ]: 12 : if (!session_.group_recvmsg(env, recv_msg, false, seq)) {
342 [ + - ][ + - ]: 6 : isc_throw(isc::config::CCSessionError,
343 : : "Incomplete response when sending drop socket command");
344 : : }
345 : :
346 : : // Answer should just be success
347 : : int rcode;
348 : : isc::data::ConstElementPtr error = isc::config::parseAnswer(rcode,
349 [ + - ]: 9 : recv_msg);
350 [ + + ]: 9 : if (rcode != 0) {
351 [ + - ][ + - ]: 6 : isc_throw(SocketError,
[ + - ][ + - ]
[ + - ]
352 : : "Error requesting release of socket: " << error->str());
353 : : }
354 : 6 : }
355 : :
356 : : private:
357 : : // Returns the domain socket file descriptor
358 : : // If we had not opened it yet, opens it now
359 : : int
360 : : getFdShareSocket(const std::string& path) {
361 [ + + ]: 24 : if (fd_share_sockets_.find(path) == fd_share_sockets_.end()) {
362 [ + + ]: 15 : const int new_fd = createFdShareSocket(path);
363 : : // Technically, the (creation and) assignment of the new map entry
364 : : // could thrown an exception and lead to FD leak. This should be
365 : : // cleaned up later (see comment about SocketSessionReceiver above)
366 [ + - ]: 3 : fd_share_sockets_[path] = new_fd;
367 : : return (new_fd);
368 : : } else {
369 [ + - ]: 9 : return (fd_share_sockets_[path]);
370 : : }
371 : : }
372 : :
373 : : // Closes the sockets that has been used for fd_share
374 : : void
375 : : closeFdShareSockets() {
376 [ + + ]: 18 : for (std::map<std::string, int>::const_iterator it =
377 : 32 : fd_share_sockets_.begin();
378 : 36 : it != fd_share_sockets_.end();
379 : : ++it) {
380 [ + - ]: 2 : close((*it).second);
381 : : }
382 : : }
383 : :
384 : : cc::AbstractSession& session_;
385 : : const std::string app_name_;
386 : : std::map<std::string, int> fd_share_sockets_;
387 : : };
388 : :
389 : : }
390 : :
391 : : SocketRequestor&
392 : 305 : socketRequestor() {
393 [ + + ]: 305 : if (requestor != NULL) {
394 : 302 : return (*requestor);
395 : : } else {
396 [ + - ]: 6 : isc_throw(InvalidOperation, "The socket requestor is not initialized");
397 : : }
398 : : }
399 : :
400 : : void
401 : 18 : initSocketRequestor(cc::AbstractSession& session,
402 : : const std::string& app_name)
403 : : {
404 [ - + ]: 18 : if (requestor != NULL) {
405 [ # # ]: 0 : isc_throw(InvalidOperation,
406 : : "The socket requestor was already initialized");
407 : : } else {
408 : 18 : requestor = new SocketRequestorCCSession(session, app_name);
409 : : }
410 : 18 : }
411 : :
412 : : void
413 : 241 : initTestSocketRequestor(SocketRequestor* new_requestor) {
414 : 241 : requestor = new_requestor;
415 : 241 : }
416 : :
417 : : void
418 : 16 : cleanupSocketRequestor() {
419 [ + - ]: 16 : if (requestor != NULL) {
420 [ + - ]: 16 : delete requestor;
421 : 16 : requestor = NULL;
422 : : } else {
423 [ # # ]: 0 : isc_throw(InvalidOperation, "The socket requestor is not initialized");
424 : : }
425 : 16 : }
426 : :
427 : : }
428 : 5 : }
|