socket_request.cc 16 KB

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