174-optimistic-data-server.txt 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. Filename: 174-optimistic-data-server.txt
  2. Title: Optimistic Data for Tor: Server Side
  3. Author: Ian Goldberg
  4. Created: 2-Aug-2010
  5. Status: Open
  6. Overview:
  7. When a SOCKS client opens a TCP connection through Tor (for an HTTP
  8. request, for example), the query latency is about 1.5x higher than it
  9. needs to be. Simply, the problem is that the sequence of data flows
  10. is this:
  11. 1. The SOCKS client opens a TCP connection to the OP
  12. 2. The SOCKS client sends a SOCKS CONNECT command
  13. 3. The OP sends a BEGIN cell to the Exit
  14. 4. The Exit opens a TCP connection to the Server
  15. 5. The Exit returns a CONNECTED cell to the OP
  16. 6. The OP returns a SOCKS CONNECTED notification to the SOCKS client
  17. 7. The SOCKS client sends some data (the GET request, for example)
  18. 8. The OP sends a DATA cell to the Exit
  19. 9. The Exit sends the GET to the server
  20. 10. The Server returns the HTTP result to the Exit
  21. 11. The Exit sends the DATA cells to the OP
  22. 12. The OP returns the HTTP result to the SOCKS client
  23. Note that the Exit node knows that the connection to the Server was
  24. successful at the end of step 4, but is unable to send the HTTP query to
  25. the server until step 9.
  26. This proposal (as well as its upcoming sibling concerning the client
  27. side) aims to reduce the latency by allowing:
  28. 1. SOCKS clients to optimistically send data before they are notified
  29. that the SOCKS connection has completed successfully
  30. 2. OPs to optimistically send DATA cells on streams in the CONNECT_WAIT
  31. state
  32. 3. Exit nodes to accept and queue DATA cells while in the
  33. EXIT_CONN_STATE_CONNECTING state
  34. This particular proposal deals with #3.
  35. In this way, the flow would be as follows:
  36. 1. The SOCKS client opens a TCP connection to the OP
  37. 2. The SOCKS client sends a SOCKS CONNECT command, followed immediately
  38. by data (such as the GET request)
  39. 3. The OP sends a BEGIN cell to the Exit, followed immediately by DATA
  40. cells
  41. 4. The Exit opens a TCP connection to the Server
  42. 5. The Exit returns a CONNECTED cell to the OP, and sends the queued GET
  43. request to the Server
  44. 6. The OP returns a SOCKS CONNECTED notification to the SOCKS client,
  45. and the Server returns the HTTP result to the Exit
  46. 7. The Exit sends the DATA cells to the OP
  47. 8. The OP returns the HTTP result to the SOCKS client
  48. Motivation:
  49. This change will save one OP<->Exit round trip (down to one from two).
  50. There are still two SOCKS Client<->OP round trips (negligible time) and
  51. two Exit<->Server round trips. Depending on the ratio of the
  52. Exit<->Server (Internet) RTT to the OP<->Exit (Tor) RTT, this will
  53. decrease the latency by 25 to 50 percent. Experiments validate these
  54. predictions. [Goldberg, PETS 2010 rump session; see
  55. https://thunk.cs.uwaterloo.ca/optimistic-data-pets2010-rump.pdf ]
  56. Design:
  57. The current code actually correctly handles queued data at the Exit; if
  58. there is queued data in a EXIT_CONN_STATE_CONNECTING stream, that data
  59. will be immediately sent when the connection succeeds. If the
  60. connection fails, the data will be correctly ignored and freed. The
  61. problem with the current server code is that the server currently
  62. drops DATA cells on streams in the EXIT_CONN_STATE_CONNECTING state.
  63. Also, if you try to queue data in the EXIT_CONN_STATE_RESOLVING state,
  64. bad things happen because streams in that state don't yet have
  65. conn->write_event set, and so some existing sanity checks (any stream
  66. with queued data is at least potentially writable) are no longer sound.
  67. The solution is to simply not drop received DATA cells while in the
  68. EXIT_CONN_STATE_CONNECTING state. Also do not send SENDME cells in this
  69. state, so that the OP cannot send more than one window's worth of data
  70. to be queued at the Exit. Finally, patch the sanity checks so that
  71. streams in the EXIT_CONN_STATE_RESOLVING state that have buffered data
  72. can pass.
  73. If no clients ever send such optimistic data, the new code will never be
  74. executed, and the behaviour of Tor will not change. When clients begin
  75. to send optimistic data, the performance of those clients' streams will
  76. improve.
  77. After discussion with nickm, it seems best to just have the server
  78. version number be the indicator of whether a particular Exit supports
  79. optimistic data. (If a client sends optimistic data to an Exit which
  80. does not support it, the data will be dropped, and the client's request
  81. will fail to complete.) What do version numbers for hypothetical future
  82. protocol-compatible implementations look like, though?
  83. Security implications:
  84. Servers (for sure the Exit, and possibly others, by watching the
  85. pattern of packets) will be able to tell that a particular client
  86. is using optimistic data. This will be discussed more in the sibling
  87. proposal.
  88. On the Exit side, servers will be queueing a little bit extra data, but
  89. no more than one window. Clients today can cause Exits to queue that
  90. much data anyway, simply by establishing a Tor connection to a slow
  91. machine, and sending one window of data.
  92. Specification:
  93. tor-spec section 6.2 currently says:
  94. The OP waits for a RELAY_CONNECTED cell before sending any data.
  95. Once a connection has been established, the OP and exit node
  96. package stream data in RELAY_DATA cells, and upon receiving such
  97. cells, echo their contents to the corresponding TCP stream.
  98. RELAY_DATA cells sent to unrecognized streams are dropped.
  99. It is not clear exactly what an "unrecognized" stream is, but this last
  100. sentence would be changed to say that RELAY_DATA cells received on a
  101. stream that has processed a RELAY_BEGIN cell and has not yet issued a
  102. RELAY_END or a RELAY_CONNECTED cell are queued; that queue is processed
  103. immediately after a RELAY_CONNECTED cell is issued for the stream, or
  104. freed after a RELAY_END cell is issued for the stream.
  105. The earlier part of this section will be addressed in the sibling
  106. proposal.
  107. Compatibility:
  108. There are compatibility issues, as mentioned above. OPs MUST NOT send
  109. optimistic data to Exit nodes whose version numbers predate (something).
  110. OPs MAY send optimistic data to Exit nodes whose version numbers match
  111. or follow that value. (But see the question about independent server
  112. reimplementations, above.)
  113. Implementation:
  114. Here is a simple patch. It seems to work with both regular streams and
  115. hidden services, but there may be other corner cases I'm not aware of.
  116. (Do streams used for directory fetches, hidden services, etc. take a
  117. different code path?)
  118. diff --git a/src/or/connection.c b/src/or/connection.c
  119. index 7b1493b..f80cd6e 100644
  120. --- a/src/or/connection.c
  121. +++ b/src/or/connection.c
  122. @@ -2845,7 +2845,13 @@ _connection_write_to_buf_impl(const char *string, size_t len,
  123. return;
  124. }
  125. - connection_start_writing(conn);
  126. + /* If we receive optimistic data in the EXIT_CONN_STATE_RESOLVING
  127. + * state, we don't want to try to write it right away, since
  128. + * conn->write_event won't be set yet. Otherwise, write data from
  129. + * this conn as the socket is available. */
  130. + if (conn->state != EXIT_CONN_STATE_RESOLVING) {
  131. + connection_start_writing(conn);
  132. + }
  133. if (zlib) {
  134. conn->outbuf_flushlen += buf_datalen(conn->outbuf) - old_datalen;
  135. } else {
  136. @@ -3382,7 +3388,11 @@ assert_connection_ok(connection_t *conn, time_t now)
  137. tor_assert(conn->s < 0);
  138. if (conn->outbuf_flushlen > 0) {
  139. - tor_assert(connection_is_writing(conn) || conn->write_blocked_on_bw ||
  140. + /* With optimistic data, we may have queued data in
  141. + * EXIT_CONN_STATE_RESOLVING while the conn is not yet marked to writing.
  142. + * */
  143. + tor_assert(conn->state == EXIT_CONN_STATE_RESOLVING ||
  144. + connection_is_writing(conn) || conn->write_blocked_on_bw ||
  145. (CONN_IS_EDGE(conn) && TO_EDGE_CONN(conn)->edge_blocked_on_circ));
  146. }
  147. diff --git a/src/or/relay.c b/src/or/relay.c
  148. index fab2d88..e45ff70 100644
  149. --- a/src/or/relay.c
  150. +++ b/src/or/relay.c
  151. @@ -1019,6 +1019,9 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
  152. relay_header_t rh;
  153. unsigned domain = layer_hint?LD_APP:LD_EXIT;
  154. int reason;
  155. + int optimistic_data = 0; /* Set to 1 if we receive data on a stream
  156. + that's in the EXIT_CONN_STATE_RESOLVING
  157. + or EXIT_CONN_STATE_CONNECTING states.*/
  158. tor_assert(cell);
  159. tor_assert(circ);
  160. @@ -1038,9 +1041,20 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
  161. /* either conn is NULL, in which case we've got a control cell, or else
  162. * conn points to the recognized stream. */
  163. - if (conn && !connection_state_is_open(TO_CONN(conn)))
  164. - return connection_edge_process_relay_cell_not_open(
  165. - &rh, cell, circ, conn, layer_hint);
  166. + if (conn && !connection_state_is_open(TO_CONN(conn))) {
  167. + if ((conn->_base.state == EXIT_CONN_STATE_CONNECTING ||
  168. + conn->_base.state == EXIT_CONN_STATE_RESOLVING) &&
  169. + rh.command == RELAY_COMMAND_DATA) {
  170. + /* We're going to allow DATA cells to be delivered to an exit
  171. + * node in state EXIT_CONN_STATE_CONNECTING or
  172. + * EXIT_CONN_STATE_RESOLVING. This speeds up HTTP, for example. */
  173. + log_warn(domain, "Optimistic data received.");
  174. + optimistic_data = 1;
  175. + } else {
  176. + return connection_edge_process_relay_cell_not_open(
  177. + &rh, cell, circ, conn, layer_hint);
  178. + }
  179. + }
  180. switch (rh.command) {
  181. case RELAY_COMMAND_DROP:
  182. @@ -1090,7 +1104,9 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
  183. log_debug(domain,"circ deliver_window now %d.", layer_hint ?
  184. layer_hint->deliver_window : circ->deliver_window);
  185. - circuit_consider_sending_sendme(circ, layer_hint);
  186. + if (!optimistic_data) {
  187. + circuit_consider_sending_sendme(circ, layer_hint);
  188. + }
  189. if (!conn) {
  190. log_info(domain,"data cell dropped, unknown stream (streamid %d).",
  191. @@ -1107,7 +1123,9 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
  192. stats_n_data_bytes_received += rh.length;
  193. connection_write_to_buf(cell->payload + RELAY_HEADER_SIZE,
  194. rh.length, TO_CONN(conn));
  195. - connection_edge_consider_sending_sendme(conn);
  196. + if (!optimistic_data) {
  197. + connection_edge_consider_sending_sendme(conn);
  198. + }
  199. return 0;
  200. case RELAY_COMMAND_END:
  201. reason = rh.length > 0 ?
  202. Performance and scalability notes:
  203. There may be more RAM used at Exit nodes, as mentioned above, but it is
  204. transient.