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