net.hpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. #ifndef __NET_HPP__
  2. #define __NET_HPP__
  3. #include <vector>
  4. #include <deque>
  5. #include <optional>
  6. #include <functional>
  7. #include <tuple>
  8. #include <boost/asio.hpp>
  9. #include <boost/thread.hpp>
  10. #include "appconfig.hpp"
  11. #include "../Enclave/enclave_api.h"
  12. // The inter-node (untrusted node to untrusted node) communication
  13. // protocol is as follows. Nodes are numbered 0 through num_nodes-1.
  14. // At startup time, each pair of nodes establishes a TCP connection by
  15. // having the lower-numbered node connect to the higher-numbered node,
  16. // and send a two-byte value of its (the sender's) node number. Once
  17. // all the connections are established, commands consist of a 5-byte
  18. // header, followed optionally by some data. The commands are listed
  19. // below. If a socket closes, we interpret that to mean the experiment
  20. // is over, and the node shuts down (which will close its own sockets,
  21. // its peers will shut down, etc.). [This isn't the best idea for a
  22. // robust long-lived deployment, of course.]
  23. //
  24. // The commands are:
  25. //
  26. // EPOCH: 0x00 + 4-byte epoch number (little-endian)
  27. //
  28. // This command is sent by the leader (typically node 0) to each other
  29. // node at the start of each epoch.
  30. //
  31. // MESSAGE: 0x01 + 4-byte total message length (little-endian)
  32. //
  33. // This command says that a number of CHUNKs comprising a single
  34. // enclave-to-enclave message will follow, whose total size will be the
  35. // given value. Note that the data itself is sent following a CHUNK
  36. // header, not a MESSAGE header, even if it's small.
  37. //
  38. // CHUNK: 0x02 + 4-byte chunk length (little-endian)
  39. // + that many bytes of data
  40. //
  41. // This command transmits the enclave-to-enclave data. The data in the
  42. // chunk will be (after the enclave-to-enclave handshake, anyway)
  43. // AES-GCM encrypted to a key known to the receiving enclave (but not
  44. // the receiving untrusted node). The chunk number (starting from 0 and
  45. // not reset between messages) will be the IV, which is not transmitted.
  46. // The 16-byte GCM tag will be the last 16 bytes of the chunk (and
  47. // included in the length in the chunk header). The sum of the chunk
  48. // lengths since the last MESSAGE command may not exceed the length in
  49. // that MESSAGE command.
  50. // Data for chunks are stored in frames. The frames are pre-allocated
  51. // to be FRAME_SIZE bytes each, and reused as much as possible by the
  52. // NodeIO class. A node will request a frame from the NodeIO, which
  53. // will return a pointer. The node will pass that pointer to the
  54. // enclave, which will write data into it, and also return to the node
  55. // how much data it wrote. The node will async_write the chunk header
  56. // and the chunk data. The async write completion handler will return
  57. // the frame to the NodeIO when the write completes.
  58. //
  59. // Headers are stored as the low 5 bytes of a uint64_t. Note that means
  60. // for headers containing sizes, the value of this uint64_t will be (for
  61. // example for the CHUNK header) (chunk_len << 8) + 0x02.
  62. using boost::asio::ip::tcp;
  63. class NodeIO {
  64. tcp::socket sock;
  65. nodenum_t node_num;
  66. using CommandTuple = std::tuple<uint64_t,uint8_t*,size_t>;
  67. std::deque<CommandTuple> commands_inflight;
  68. std::deque<uint8_t *> frames_available;
  69. // The frames and commands are used and returned by different
  70. // threads, so we protect them with a mutex each
  71. boost::mutex frame_deque_lock, commands_deque_lock;
  72. // The claimed size of the message currently being sent in chunks
  73. uint32_t msgsize_inflight;
  74. // The total size of the chunks so far we've sent for this message
  75. uint32_t chunksize_inflight;
  76. // As above, but for incoming messages and chunks
  77. uint32_t recv_msgsize_inflight;
  78. uint32_t recv_chunksize_inflight;
  79. // The static uint64_t used to receive a header
  80. uint64_t receive_header;
  81. // The static frame used to receive a chunk
  82. uint8_t receive_frame[FRAME_SIZE];
  83. uint64_t bytes_sent; // count bytes sent
  84. void send_header_data(uint64_t header, uint8_t *data, size_t len);
  85. // Asynchronously send the first message from the command queue.
  86. // * The command_deque_lock must be held when this is called! *
  87. // This method may be called from either thread (the work thread or
  88. // the async_write handler thread).
  89. void async_send_commands();
  90. public:
  91. NodeIO(tcp::socket &&socket, nodenum_t node_num);
  92. uint8_t *request_frame();
  93. void return_frame(uint8_t* frame);
  94. void send_epoch(uint32_t epoch_num);
  95. void send_message_header(uint32_t tot_message_len);
  96. // Returns true if there are more chunks to send in this message,
  97. // false if not.
  98. bool send_chunk(uint8_t *data, uint32_t chunk_len);
  99. // Asynchronously receive commands from this socket. Depending on
  100. // what they are, one of the three callbacks will be called. The
  101. // callbacks may be called from a different thread. The data
  102. // pointer in chunk_cb is to a _static_ frame that's only used for
  103. // receiving. Be sure to do whatever you need to do with the
  104. // contents (typically, pass it to the enclave) before calling this
  105. // function again.
  106. void recv_commands(
  107. std::function<void(boost::system::error_code)> error_cb,
  108. std::function<void(uint32_t)> epoch_cb);
  109. // Close the socket
  110. void close() { sock.close(); }
  111. uint64_t reset_bytes_sent();
  112. };
  113. class NetIO {
  114. boost::asio::io_context &context;
  115. const Config &conf;
  116. const NodeConfig &myconf;
  117. std::deque<std::optional<NodeIO>> nodeios;
  118. std::shared_ptr<tcp::acceptor> ingestion_acceptor;
  119. std::shared_ptr<tcp::acceptor> storage_acceptor;
  120. size_t auth_size, msgbundle_size;
  121. void ing_receive_msgbundle(tcp::socket* socket, clientid_t c_simid);
  122. void ing_authenticate_new_client(tcp::socket* socket,
  123. const boost::system::error_code& error);
  124. void ing_start_accept();
  125. std::vector<tcp::socket*> client_sockets;
  126. uint32_t num_clients_per_stg;
  127. unsigned char *epoch_tokens;
  128. unsigned char *epoch_mailboxes;
  129. uint16_t num_stg_nodes;
  130. uint32_t token_bundle_size;
  131. uint32_t mailbox_size;
  132. void stg_authenticate_new_client(tcp::socket* socket,
  133. const boost::system::error_code& error);
  134. void stg_start_accept();
  135. public:
  136. NetIO(boost::asio::io_context &io_context, const Config &config);
  137. nodenum_t num_nodes;
  138. nodenum_t me;
  139. NodeIO &node(nodenum_t node_num) {
  140. assert(node_num < num_nodes);
  141. return nodeios[node_num].value();
  142. }
  143. const Config &config() { return conf; }
  144. const NodeConfig &myconfig() { return myconf; }
  145. boost::asio::io_context &io_context() { return context; }
  146. // Call recv_commands with these arguments on each of the nodes (not
  147. // including ourselves)
  148. void recv_commands(
  149. std::function<void(boost::system::error_code)> error_cb,
  150. std::function<void(uint32_t)> epoch_cb);
  151. void send_client_mailbox();
  152. // Close all the sockets
  153. void close();
  154. uint64_t reset_bytes_sent();
  155. #ifdef TRACE_SOCKIO
  156. private:
  157. struct timeval last_ing;
  158. size_t num_ing;
  159. #endif
  160. };
  161. extern NetIO *g_netio;
  162. extern size_t client_count;
  163. #endif