commit 49302db0cd095bbc02c631e1628542f4d8b003a9 Author: Zack Weinberg zackw@panix.com Date: Sat Mar 24 20:27:50 2012 -0700
Revise chop.cc to be closer to what the paper says it does:
* new block format * 32-bit circuit IDs * separate header and payload encryption * sequence numbers count by block, not byte
Still to do:
* proper handshakes * rekeying * "exactly what steg wants" block padding --- src/crypt.cc | 4 +- src/protocol/chop.cc | 2059 +++++++++++++++++++++++++------------------------- 2 files changed, 1038 insertions(+), 1025 deletions(-)
diff --git a/src/crypt.cc b/src/crypt.cc index d6eed49..e81e8d4 100644 --- a/src/crypt.cc +++ b/src/crypt.cc @@ -273,9 +273,7 @@ key_generator::from_passphrase(const uint8_t *phra, size_t plen, // to just feed its output directly to the HKDF-Expand phase; an // alternative would be to run PBKDF2 on the passphrase without a // salt, then put the result through HKDF-Extract with the salt. - // - // 1000 iterations or 50 ms, whichever is more - extractor.DeriveKey(prk, SHA256_LEN, 0, phra, plen, salt, slen, 1000, 0.05); + extractor.DeriveKey(prk, SHA256_LEN, 0, phra, plen, salt, slen, 1000);
key_generator *r = new key_generator_impl(prk, ctxt, clen); memset(prk, 0, SHA256_LEN); diff --git a/src/protocol/chop.cc b/src/protocol/chop.cc index b4ec692..e81214a 100644 --- a/src/protocol/chop.cc +++ b/src/protocol/chop.cc @@ -1,10 +1,9 @@ -/* Copyright 2011 Zack Weinberg +/* Copyright 2011, 2012 Zack Weinberg See LICENSE for other credits and copying information
The chopper is the core StegoTorus protocol implementation. - For its design, see doc/chopper.tex. Note that it is still - being implemented, and many things that are *intended* to change - from the toy "roundrobin" protocol have not yet changed. */ + For its design, see doc/chopper.txt. Note that it is still + being implemented, and may change incompatibly. */
#include "util.h" #include "connections.h" @@ -20,805 +19,363 @@ #include <event2/event.h> #include <event2/buffer.h>
+#ifdef HAVE_EXECINFO_H +#include <execinfo.h> +#endif + using std::tr1::unordered_map; using std::tr1::unordered_set; using std::vector; +using std::make_pair;
-/* Header serialization and deserialization */ - -struct chop_header +namespace { - uint64_t ckt_id; - uint8_t pkt_iv[8]; - uint32_t offset; - uint16_t length; - uint16_t flags; -}; - -#define CHOP_WIRE_HDR_LEN (sizeof(struct chop_header)) -#define CHOP_BLOCK_OVERHD (CHOP_WIRE_HDR_LEN + GCM_TAG_LEN) -#define CHOP_MAX_DATA (65535 - CHOP_BLOCK_OVERHD) -#define CHOP_MAX_CHAFF 2048
-#define CHOP_F_SYN 0x0001 -#define CHOP_F_FIN 0x0002 -#define CHOP_F_CHAFF 0x0004 -/* further flags values are reserved */ - -/* Reassembly queue. This is a doubly-linked circular list with a - sentinel element at the head (identified by data == 0). List - entries are sorted by offset. Gaps in so-far-received data - are "in between" entries in the list. */ - -struct chop_reassembly_elt +/* Packets on the wire have a 16-byte header, consisting of a 32-bit + sequence number, two 16-bit length fields ("D" and "P"), an 8-bit + opcode ("F"), and a 56-bit check field. All numbers in this header + are serialized in network byte order. + + | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | + |Sequence Number| D | P | F | Check | + + The header is encrypted with AES in ECB mode: this is safe because + the header is exactly one AES block long, the sequence number is + never repeated, the header-encryption key is not used for anything + else, and the high 24 bits of the sequence number, plus the check + field, constitute an 80-bit MAC. The receiver maintains a + 256-element sliding window of acceptable sequence numbers, which + begins one after the highest sequence number so far _processed_ + (not received). If the sequence number is outside this window, or + the check field is not all-bits-zero, the packet is discarded. An + attacker's odds of being able to manipulate the D, P, or F fields + or the low bits of the sequence number are therefore less than one + in 2^80. Unlike TCP, our sequence numbers always start at zero on + a new (or freshly rekeyed) circuit, and increment by one per + _block_, not per byte of data. Furthermore, they do not wrap: a + rekeying cycle (which resets the sequence number) is required to + occur before the highest-received sequence number reaches 2^32. + + Following the header are two variable-length payload sections, + "data" and "padding", whose length in bytes are given by the D and + P fields, respectively. These sections are encrypted, using a + different key, with AES in GCM mode. The *encrypted* packet header + doubles as the GCM nonce. The semantics of the "data" section's + contents, if any, are defined by the opcode F. The "padding" + section SHOULD be filled with zeroes by the sender; regardless, its + contents MUST be ignored by the receiver. Following these sections + is a 16-byte GCM authentication tag, computed over the data and + padding sections only, NOT the message header. */ + +const size_t HEADER_LEN = 16; +const size_t TRAILER_LEN = 16; +const size_t SECTION_LEN = UINT16_MAX; +const size_t MIN_BLOCK_SIZE = HEADER_LEN + TRAILER_LEN; +const size_t MAX_BLOCK_SIZE = MIN_BLOCK_SIZE + SECTION_LEN*2; + +enum opcode_t { - struct chop_reassembly_elt *prev; - struct chop_reassembly_elt *next; - struct evbuffer *data; - uint32_t offset; - uint16_t length; - uint16_t flags; + op_DAT = 0, // Pass data section along to upstream + op_FIN = 1, // No further transmissions (pass data along if any) + op_RST = 2, // Protocol error, close circuit now + op_RK1 = 3, // Commence rekeying + op_RK2 = 4, // Continue rekeying + op_RK3 = 5, // Conclude rekeying + op_RESERVED0 = 6, // 6 -- 127 reserved for future definition + op_STEG0 = 128, // 128 -- 255 reserved for steganography modules + op_LAST = 255 };
-/* Horrifically crude "encryption". Uses a compiled-in pair of - encryption keys, no MAC, and recycles the circuit ID as a - partial IV. To be replaced with something less laughable ASAP. */ - -static const uint8_t c2s_key[] = - "\x44\x69\x5f\x45\x41\x67\xe9\x69\x14\x6c\x5f\xd2\x41\x63\xc4\x02"; -static const uint8_t s2c_key[] = - "\xfa\x31\x78\x6c\xb9\x4c\x66\x2a\xd0\x30\x59\xf7\x28\x22\x2f\x22"; - -/* Connections and circuits */ - -namespace { - struct chop_config_t; - struct chop_circuit_t; - typedef unordered_map<uint64_t, chop_circuit_t *> chop_circuit_table; - - struct chop_conn_t : conn_t - { - chop_config_t *config; - chop_circuit_t *upstream; - steg_t *steg; - struct evbuffer *recv_pending; - struct event *must_transmit_timer; - bool no_more_transmissions : 1; - - CONN_DECLARE_METHODS(chop); - }; +class block_header +{ + uint8_t clear[16]; + uint8_t ciphr[16];
- struct chop_circuit_t : circuit_t +public: + block_header(uint32_t s, uint16_t d, uint16_t p, opcode_t f, + ecb_encryptor &ec) { - chop_reassembly_elt reassembly_queue; - unordered_set<chop_conn_t *> downstreams; - gcm_encryptor *send_crypt; - gcm_decryptor *recv_crypt; - chop_config_t *config; - - uint64_t circuit_id; - uint32_t send_offset; - uint32_t recv_offset; - uint32_t dead_cycles; - bool received_syn : 1; - bool received_fin : 1; - bool sent_syn : 1; - bool sent_fin : 1; - bool upstream_eof : 1; - - CIRCUIT_DECLARE_METHODS(chop); - - uint32_t axe_interval() { - // This function must always return a number which is larger than - // the maximum possible number that *our peer's* flush_interval() - // could have returned; otherwise, we might axe the connection when - // it was just that there was nothing to say for a while. - // For simplicity's sake, right now we hardwire this to be 30 minutes. - return 30 * 60 * 1000; - } - uint32_t flush_interval() { - // 10*60*1000 lies between 2^19 and 2^20. - uint32_t shift = std::max(1u, std::min(19u, dead_cycles)); - uint32_t xv = std::max(1u, std::min(10u * 60 * 1000, 1u << shift)); - return rng_range_geom(20 * 60 * 1000, xv) + 100; + if (f > op_LAST || (f >= op_RESERVED0 && f < op_STEG0)) { + memset(clear, 0xFF, sizeof clear); // invalid! + memset(ciphr, 0xFF, sizeof ciphr); + return; } - }; - - struct chop_config_t : config_t - { - struct evutil_addrinfo *up_address; - vector<struct evutil_addrinfo *> down_addresses; - vector<const char *> steg_targets; - chop_circuit_table circuits;
- CONFIG_DECLARE_METHODS(chop); - }; -} - -PROTO_DEFINE_MODULE(chop); + // sequence number + clear[0] = (s >> 24) & 0xFF; + clear[1] = (s >> 16) & 0xFF; + clear[2] = (s >> 8) & 0xFF; + clear[3] = (s ) & 0xFF;
-/* Header serialization and deserialization */ + // D field + clear[4] = (d >> 8) & 0xFF; + clear[5] = (d ) & 0xFF;
-static void -chop_write_header(uint8_t *wire_header, const struct chop_header *hdr) -{ - /* bits on the wire are in network byte order */ - wire_header[ 0] = (hdr->ckt_id & 0xFF00000000000000ull) >> 56; - wire_header[ 1] = (hdr->ckt_id & 0x00FF000000000000ull) >> 48; - wire_header[ 2] = (hdr->ckt_id & 0x0000FF0000000000ull) >> 40; - wire_header[ 3] = (hdr->ckt_id & 0x000000FF00000000ull) >> 32; - wire_header[ 4] = (hdr->ckt_id & 0x00000000FF000000ull) >> 24; - wire_header[ 5] = (hdr->ckt_id & 0x0000000000FF0000ull) >> 16; - wire_header[ 6] = (hdr->ckt_id & 0x000000000000FF00ull) >> 8; - wire_header[ 7] = (hdr->ckt_id & 0x00000000000000FFull) >> 0; - - wire_header[ 8] = hdr->pkt_iv[0]; - wire_header[ 9] = hdr->pkt_iv[1]; - wire_header[10] = hdr->pkt_iv[2]; - wire_header[11] = hdr->pkt_iv[3]; - wire_header[12] = hdr->pkt_iv[4]; - wire_header[13] = hdr->pkt_iv[5]; - wire_header[14] = hdr->pkt_iv[6]; - wire_header[15] = hdr->pkt_iv[7]; - - wire_header[16] = (hdr->offset & 0xFF000000u) >> 24; - wire_header[17] = (hdr->offset & 0x00FF0000u) >> 16; - wire_header[18] = (hdr->offset & 0x0000FF00u) >> 8; - wire_header[19] = (hdr->offset & 0x000000FFu) >> 0; - - wire_header[20] = (hdr->length & 0xFF00u) >> 8; - wire_header[21] = (hdr->length & 0x00FFu) >> 0; - wire_header[22] = (hdr->flags & 0xFF00u) >> 8; - wire_header[23] = (hdr->flags & 0x00FFu) >> 0; -} + // P field + clear[6] = (p >> 8) & 0xFF; + clear[7] = (p ) & 0xFF;
-static int -chop_peek_circuit_id(struct evbuffer *buf, struct chop_header *hdr) -{ - uint8_t wire_id[8]; - if (evbuffer_copyout(buf, wire_id, 8) != 8) - return -1; - hdr->ckt_id = ((((uint64_t)wire_id[ 0]) << 56) + - (((uint64_t)wire_id[ 1]) << 48) + - (((uint64_t)wire_id[ 2]) << 40) + - (((uint64_t)wire_id[ 3]) << 32) + - (((uint64_t)wire_id[ 4]) << 24) + - (((uint64_t)wire_id[ 5]) << 16) + - (((uint64_t)wire_id[ 6]) << 8) + - (((uint64_t)wire_id[ 7]) << 0)); - return 0; -} + // F field + clear[8] = uint8_t(f);
-static int -chop_decrypt_header(chop_circuit_t *ckt, - struct evbuffer *buf, - struct chop_header *hdr) -{ - uint8_t wire_header[CHOP_WIRE_HDR_LEN]; - uint8_t decoded_header[CHOP_WIRE_HDR_LEN-16]; + // Check field + memset(clear + 9, 0, 7);
- if (evbuffer_copyout(buf, wire_header, CHOP_WIRE_HDR_LEN) - != CHOP_WIRE_HDR_LEN) { - log_warn("not enough data copied out"); - return -1; + ec.encrypt(ciphr, clear); }
- hdr->ckt_id = ((((uint64_t)wire_header[ 0]) << 56) + - (((uint64_t)wire_header[ 1]) << 48) + - (((uint64_t)wire_header[ 2]) << 40) + - (((uint64_t)wire_header[ 3]) << 32) + - (((uint64_t)wire_header[ 4]) << 24) + - (((uint64_t)wire_header[ 5]) << 16) + - (((uint64_t)wire_header[ 6]) << 8) + - (((uint64_t)wire_header[ 7]) << 0)); - - hdr->pkt_iv[0] = wire_header[ 8]; - hdr->pkt_iv[1] = wire_header[ 9]; - hdr->pkt_iv[2] = wire_header[10]; - hdr->pkt_iv[3] = wire_header[11]; - hdr->pkt_iv[4] = wire_header[12]; - hdr->pkt_iv[5] = wire_header[13]; - hdr->pkt_iv[6] = wire_header[14]; - hdr->pkt_iv[7] = wire_header[15]; - - /* The full IV is the circuit ID plus packet ID *as it is on the - wire*. */ - ckt->recv_crypt->decrypt_unchecked(decoded_header, - wire_header + 16, CHOP_WIRE_HDR_LEN - 16, - wire_header, 16); - - hdr->offset = ((((uint32_t)decoded_header[0]) << 24) + - (((uint32_t)decoded_header[1]) << 16) + - (((uint32_t)decoded_header[2]) << 8) + - (((uint32_t)decoded_header[3]) << 0)); - - hdr->length = ((((uint16_t)decoded_header[4]) << 8) + - (((uint16_t)decoded_header[5]) << 0)); - - hdr->flags = ((((uint16_t)decoded_header[6]) << 8) + - (((uint16_t)decoded_header[7]) << 0)); - - log_debug("decoded offset %u length %hu flags %04hx", - hdr->offset, hdr->length, hdr->flags); - return 0; -} - -/* Transmit subroutines. */ - -static chop_conn_t * -chop_pick_connection(chop_circuit_t *ckt, size_t desired, size_t *blocksize) -{ - size_t maxbelow = 0; - size_t minabove = SIZE_MAX; - chop_conn_t *targbelow = NULL; - chop_conn_t *targabove = NULL; - - if (desired > CHOP_MAX_DATA) - desired = CHOP_MAX_DATA; - - /* Find the best fit for the desired transmission from all the - outbound connections' transmit rooms. */ - for (unordered_set<chop_conn_t *>::iterator i = ckt->downstreams.begin(); - i != ckt->downstreams.end(); i++) { - chop_conn_t *conn = *i; - /* We can only use candidates that have a steg target already. */ - if (conn->steg) { - /* Find the connections whose transmit rooms are closest to the - desired transmission length from both directions. */ - size_t room = conn->steg->transmit_room(conn); - log_debug(conn, "offers %lu bytes (%s)", (unsigned long)room, - conn->steg->name()); - - if (room <= CHOP_BLOCK_OVERHD) - room = 0; - else - room -= CHOP_BLOCK_OVERHD; - - if (room > CHOP_MAX_DATA) - room = CHOP_MAX_DATA; - - if (room >= desired) { - if (room < minabove) { - minabove = room; - targabove = conn; - } - } else { - if (room > maxbelow) { - maxbelow = room; - targbelow = conn; - } - } - } else { - log_debug(conn, "offers 0 bytes (no steg)"); + block_header(evbuffer *buf, ecb_decryptor &dc) + { + if (evbuffer_copyout(buf, ciphr, sizeof ciphr) != sizeof ciphr) { + memset(clear, 0xFF, sizeof clear); + memset(ciphr, 0xFF, sizeof ciphr); + return; } + dc.decrypt(clear, ciphr); }
- /* If we have a connection that can take all the data, use it. - Otherwise, use the connection that can take as much of the data - as possible. As a special case, if no connection can take data, - targbelow, targabove, maxbelow, and minabove will all still have - their initial values, so we'll return NULL and set blocksize to 0, - which callers know how to handle. */ - if (targabove) { - *blocksize = minabove; - return targabove; - } else { - *blocksize = maxbelow; - return targbelow; - } -} - -static int -chop_send_block(chop_conn_t *dest, - chop_circuit_t *ckt, - struct evbuffer *source, - struct evbuffer *block, - uint16_t length, - uint16_t flags) -{ - chop_header hdr; - struct evbuffer_iovec v; - uint8_t *p; - - log_assert(evbuffer_get_length(block) == 0); - log_assert(evbuffer_get_length(source) >= length); - log_assert(dest->steg); - - /* We take special care not to modify 'source' if any step fails. */ - if (evbuffer_reserve_space(block, - length + CHOP_WIRE_HDR_LEN + GCM_TAG_LEN, - &v, 1) != 1) - return -1; - if (v.iov_len < length + CHOP_WIRE_HDR_LEN + GCM_TAG_LEN) - goto fail; - - v.iov_len = length + CHOP_WIRE_HDR_LEN + GCM_TAG_LEN; - - hdr.ckt_id = ckt->circuit_id; - hdr.offset = ckt->send_offset; - hdr.length = length; - hdr.flags = flags; - rng_bytes(hdr.pkt_iv, 8); - chop_write_header((uint8_t*)v.iov_base, &hdr); - - if (evbuffer_copyout(source, (uint8_t *)v.iov_base + CHOP_WIRE_HDR_LEN, - length) != length) - goto fail; - - p = (uint8_t *)v.iov_base; - ckt->send_crypt->encrypt(p + 16, p + 16, length + CHOP_WIRE_HDR_LEN - 16, - p, 16); - - if (evbuffer_commit_space(block, &v, 1)) - goto fail; - - if (dest->steg->transmit(block, dest)) - goto fail_committed; - - if (evbuffer_drain(source, length)) - /* this really should never happen, and we can't recover from it */ - log_abort(dest, "evbuffer_drain failed"); /* does not return */ - - /* Cancel the must-transmit timer if it's pending; we have transmitted. */ - if (dest->must_transmit_timer) - evtimer_del(dest->must_transmit_timer); - - if (!(flags & CHOP_F_CHAFF)) - ckt->send_offset += length; - if (flags & CHOP_F_SYN) - ckt->sent_syn = true; - if (flags & CHOP_F_FIN) - ckt->sent_fin = true; - log_debug(dest, "sent %lu+%u byte block [flags %04hx]", - (unsigned long)CHOP_WIRE_HDR_LEN, length, flags); - - return 0; - - fail: - v.iov_len = 0; - evbuffer_commit_space(block, &v, 1); - fail_committed: - evbuffer_drain(block, evbuffer_get_length(block)); - log_warn(dest, "allocation or buffer copy failed"); - - return -1; -} - -static int -chop_send_blocks(chop_circuit_t *ckt) -{ - struct evbuffer *xmit_pending = bufferevent_get_input(ckt->up_buffer); - struct evbuffer *block; - chop_conn_t *target; - size_t avail; - size_t blocksize; - uint16_t flags; + uint32_t seqno() const + { + return ((uint32_t(clear[0]) << 24) | + (uint32_t(clear[1]) << 16) | + (uint32_t(clear[2]) << 8) | + (uint32_t(clear[3]) ));
- if (!(block = evbuffer_new())) { - log_warn(ckt, "allocation failure"); - return -1; }
- for (;;) { - avail = evbuffer_get_length(xmit_pending); - flags = ckt->sent_syn ? 0 : CHOP_F_SYN; - - log_debug(ckt, "%lu bytes to send", (unsigned long)avail); - - if (avail == 0) - break; - - target = chop_pick_connection(ckt, avail, &blocksize); - if (!target) { - log_debug(ckt, "no target connection available"); - /* this is not an error; it can happen e.g. when the server has - something to send immediately and the client hasn't spoken yet */ - break; - } - - if (avail <= blocksize) { - blocksize = avail; - if (ckt->upstream_eof && !ckt->sent_fin) - flags |= CHOP_F_FIN; - } - - if (chop_send_block(target, ckt, xmit_pending, block, blocksize, flags)) { - evbuffer_free(block); - return -1; - } + size_t dlen() const + { + return ((uint16_t(clear[4]) << 8) | + (uint16_t(clear[5]) )); }
- evbuffer_free(block); - avail = evbuffer_get_length(xmit_pending); - if (avail) - log_debug(ckt, "%lu bytes still waiting to be sent", (unsigned long)avail); - return 0; -} - -static int -chop_send_targeted(chop_circuit_t *ckt, chop_conn_t *target, size_t blocksize) -{ - struct evbuffer *xmit_pending = bufferevent_get_input(ckt->up_buffer); - size_t avail = evbuffer_get_length(xmit_pending); - struct evbuffer *block = evbuffer_new(); - uint16_t flags = 0; - - log_debug(target, "%lu bytes available, %lu bytes room", - (unsigned long)avail, (unsigned long)blocksize); - if (!block) { - log_warn(target, "allocation failure"); - return -1; + size_t plen() const + { + return ((uint16_t(clear[6]) << 8) | + (uint16_t(clear[7]) )); }
- if (!ckt->sent_syn) - flags |= CHOP_F_SYN; - - if (avail) { - if (avail <= blocksize) { - blocksize = avail; - if (ckt->upstream_eof && !ckt->sent_fin) - flags |= CHOP_F_FIN; - } - - - if (chop_send_block(target, ckt, xmit_pending, block, blocksize, flags)) { - evbuffer_free(block); - return -1; - } - - evbuffer_free(block); - avail = evbuffer_get_length(xmit_pending); - if (avail) - log_debug(ckt, "%lu bytes still waiting to be sent", - (unsigned long)avail); - return 0; - - } else { - struct evbuffer *chaff; - struct evbuffer_iovec v; - - if (blocksize > CHOP_MAX_CHAFF) - blocksize = CHOP_MAX_CHAFF; - - blocksize = rng_range(1, blocksize + 1); - log_debug(target, "generating %lu bytes chaff", (unsigned long)blocksize); - - chaff = evbuffer_new(); - if (!chaff || - evbuffer_reserve_space(chaff, blocksize, &v, 1) != 1 || - v.iov_len < blocksize) - goto fail; - - v.iov_len = blocksize; - memset(v.iov_base, 0, v.iov_len); - if (evbuffer_commit_space(chaff, &v, 1)) - goto fail; - - flags |= CHOP_F_CHAFF; - if (ckt->upstream_eof && !ckt->sent_fin) - flags |= CHOP_F_FIN; - if (chop_send_block(target, ckt, chaff, block, blocksize, flags)) - goto fail; - - evbuffer_free(chaff); - evbuffer_free(block); - return 0; - - fail: - log_warn(target, "failed to construct chaff block"); - if (chaff) evbuffer_free(chaff); - if (block) evbuffer_free(block); - return -1; + size_t total_len() const + { + return HEADER_LEN + TRAILER_LEN + dlen() + plen(); } -} - -static int -chop_send_chaff(chop_circuit_t *ckt) -{ - size_t room;
- chop_conn_t *target = chop_pick_connection(ckt, 1, &room); - if (!target) { - /* If we have connections and we can't send, that means we're waiting - for the server to respond. Just wait. */ - return 0; + opcode_t opcode() const + { + return opcode_t(clear[8]); } - return chop_send_targeted(ckt, target, room); -} - -static void -must_transmit_timer_cb(evutil_socket_t, short, void *arg) -{ - chop_conn_t *conn = static_cast<chop_conn_t*>(arg); - size_t room;
- if (!conn->upstream) { - log_debug(conn, "must transmit, but no circuit (stale connection)"); - conn_do_flush(conn); - return; + bool valid(uint64_t window) const + { + // This check must run in constant time. + uint8_t ck = (clear[ 9] | clear[10] | clear[11] | clear[12] | + clear[13] | clear[14] | clear[15]); + uint32_t delta = seqno() - window; + ck |= !!(delta & ~uint32_t(0xFF)); + return !ck; }
- if (!conn->steg) { - log_warn(conn, "must transmit, but no steg module available"); - return; - } - room = conn->steg->transmit_room(conn); - if (room <= CHOP_BLOCK_OVERHD) { - log_warn(conn, "must transmit, but no transmit room"); - return; + const uint8_t *nonce() const + { + return ciphr; }
- log_debug(conn, "must transmit"); - chop_send_targeted(conn->upstream, conn, room - CHOP_BLOCK_OVERHD); -} + const uint8_t *cleartext() const + { + return clear; + } +};
-/* Receive subroutines. */ +/* Most of a block's header information is processed before it reaches + the reassembly queue; the only things the queue needs to record are + the sequence number (which is stored implictly), the opcode, and an + evbuffer holding the data section. Zero-data blocks still get an + evbuffer, for simplicity's sake: a reassembly queue element holds a + received block if and only if its data pointer is non-null.
-/* True if s < t (mod 2**32). */ -static inline bool -mod32_lt(uint32_t s, uint32_t t) -{ - uint32_t d = t - s; - return 0 < d && d < 0x80000000u; -} + The reassembly queue is a 256-element circular buffer of + 'reassembly_elt' structs. This corresponds to the 256-element + sliding window of sequence numbers which may legitimately be + received at any time. */
-/* True if s <= t (mod 2**32). */ -static inline bool -mod32_le(uint32_t s, uint32_t t) +struct reassembly_elt { - uint32_t d = t - s; - return d < 0x80000000u; -} + evbuffer *data; + opcode_t op; +};
-/** Add BLOCK to the reassembly queue at the appropriate location - and merge adjacent blocks to the extent possible. */ -static int -chop_reassemble_block(chop_circuit_t *ckt, struct evbuffer *block, - chop_header *hdr) +class reassembly_queue { - chop_reassembly_elt *queue = &ckt->reassembly_queue; - chop_reassembly_elt *p, *q; - - if (hdr->flags & CHOP_F_CHAFF) { - /* Chaff goes on the reassembly queue if it carries any flags that - must be processed in sequence (SYN, FIN), but we throw away its - contents. Doing all chaff-handling here simplifies the caller - at the expense of slightly more buffer-management overhead. */ - if (!(hdr->flags & (CHOP_F_SYN|CHOP_F_FIN))) { - log_debug(ckt, "discarding chaff with no flags"); - evbuffer_free(block); - return 0; - } + reassembly_elt cbuf[256]; + uint32_t next_to_process;
- hdr->length = 0; - evbuffer_drain(block, evbuffer_get_length(block)); - log_debug(ckt, "chaff with flags, treating length as 0"); - } + reassembly_queue(const reassembly_queue&) DELETE_METHOD; + reassembly_queue& operator=(const reassembly_queue&) DELETE_METHOD;
- /* SYN must occur at offset zero, may not be duplicated, and if we - already have anything on the reassembly queue, it must come - logically after this block. */ - if ((hdr->flags & CHOP_F_SYN) && - (hdr->offset > 0 || - (queue->next != queue && - ((queue->next->flags & CHOP_F_SYN) || - !mod32_le(hdr->offset + hdr->length, queue->next->offset))))) { - log_warn(ckt, "protocol error: inappropriate SYN block"); - return -1; +public: + reassembly_queue() + : next_to_process(0) + { + memset(cbuf, 0, sizeof cbuf); }
- /* FIN may not be duplicated and must occur logically after everything - we've already received. */ - if ((hdr->flags & CHOP_F_FIN) && queue->prev != queue && - ((queue->prev->flags & CHOP_F_FIN) || - !mod32_le(queue->prev->offset + queue->prev->length, hdr->offset))) { - log_warn(ckt, "protocol error: inappropriate FIN block"); - return -1; + ~reassembly_queue() + { + for (int i = 0; i < 256; i++) + if (cbuf[i].data) + evbuffer_free(cbuf[i].data); }
- /* Non-SYN/FIN must come after any SYN block presently in the queue - and before any FIN block presently in the queue. */ - if (!(hdr->flags & (CHOP_F_SYN|CHOP_F_FIN)) && queue->next != queue && - (((queue->next->flags & CHOP_F_SYN) && - !mod32_le(queue->next->offset + queue->next->length, hdr->offset)) || - ((queue->prev->flags & CHOP_F_FIN) && - !mod32_le(hdr->offset + hdr->length, queue->prev->offset)))) { - log_warn(ckt, "protocol error: inappropriate normal block"); - return -1; + // Remove the next block to be processed from the reassembly queue + // and return it. If we are out of blocks or the next block to + // process has not yet arrived, return an empty reassembly_elt. + // Caller is responsible for freeing the evbuffer in the + // reassembly_elt, if any. + reassembly_elt + remove_next() + { + reassembly_elt rv = { 0, op_DAT }; + uint8_t front = next_to_process & 0xFF; + if (cbuf[front].data) { + rv = cbuf[front]; + cbuf[front].data = 0; + cbuf[front].op = op_DAT; + next_to_process++; + } + return rv; }
- for (p = queue->next; p != queue; p = p->next) { - /* Try first to merge the new block into an existing one. */ - if (hdr->offset + hdr->length == p->offset) - goto grow_front; - - if (hdr->offset == p->offset + p->length) - goto grow_back; - - /* Does this block fit in between 'p->prev' and 'p'? - Note: if 'p->prev->data' is NULL, it is the sentinel, - and p->prev->offset is meaningless. */ - if (mod32_lt(hdr->offset + hdr->length, p->offset)) { - if (!p->prev->data || - mod32_lt(p->prev->offset + p->prev->length, hdr->offset)) - break; - - /* protocol error: this block goes before 'p' but does not fit - after 'p->prev' */ - log_warn(ckt, "protocol error: %u byte block does not fit at offset %u", - hdr->length, hdr->offset); - return -1; + // Insert a block into the reassembly queue at sequence number + // SEQNO, with opcode OP and data section DATA. Returns true if the + // block was successfully added to the queue, false if it is either + // outside the acceptable window or duplicates a block already on + // the queue (both of these cases indicate protocol errors). + // DATA is consumed no matter what the return value is. + bool + insert(uint32_t seqno, opcode_t op, evbuffer *data, conn_t *conn) + { + if (seqno - window() > 255) { + log_warn(conn, "block outside receive window"); + evbuffer_free(data); + return false; + } + uint8_t front = next_to_process & 0xFF; + uint8_t pos = front + (seqno - window()); + if (cbuf[pos].data) { + log_warn(conn, "duplicate block"); + evbuffer_free(data); + return false; } - }
- /* This block goes before, but does not merge with, 'p'. - Special case: if 'p' is the sentinel, we have not yet checked - that this block goes after the last block in the list (aka p->prev). */ - if (!p->data && p->prev->data && - !mod32_lt(p->prev->offset + p->prev->length, hdr->offset)) { - log_warn(ckt, "protocol error: %u byte block does not fit at offset %u " - "(sentinel case)", - hdr->length, hdr->offset); - return -1; + cbuf[pos].data = data; + cbuf[pos].op = op; + return true; }
- q = (chop_reassembly_elt *)xzalloc(sizeof(chop_reassembly_elt)); - q->data = block; - q->offset = hdr->offset; - q->length = hdr->length; - q->flags = hdr->flags; + // Return the current lowest acceptable sequence number in the + // receive window. This is the value to be passed to + // block_header::valid(). + uint32_t window() const { return next_to_process; }
- q->prev = p->prev; - q->next = p; - q->prev->next = q; - q->next->prev = q; - return 0; - - grow_back: - if (evbuffer_add_buffer(p->data, block)) { - log_warn(ckt, "failed to append to existing buffer"); - return -1; - } - evbuffer_free(block); - p->length += hdr->length; - p->flags |= hdr->flags; - - /* Can we now combine 'p' with its successor? */ - while (p->next->data && p->offset + p->length == p->next->offset) { - q = p->next; - if (evbuffer_add_buffer(p->data, q->data)) { - log_warn(ckt, "failed to merge buffers"); - return -1; + // As the last step of a rekeying cycle, the expected next sequence number + // is reset to zero. + void reset() + { + for (int i = 0; i < 256; i++) { + log_assert(!cbuf[i].data); } - p->length += q->length; - p->flags |= q->flags; - - evbuffer_free(q->data); - q->next->prev = q->prev; - q->prev->next = q->next; - free(q); + next_to_process = 0; } - return 0; +};
- grow_front: - if (evbuffer_prepend_buffer(p->data, block)) { - log_warn(ckt, "failed to prepend to existing buffer"); - return -1; - } - evbuffer_free(block); - p->length += hdr->length; - p->offset -= hdr->length; - p->flags |= hdr->flags; - - /* Can we now combine 'p' with its predecessor? */ - while (p->prev->data && p->offset == p->prev->offset + p->prev->length) { - q = p->prev; - if (evbuffer_prepend_buffer(p->data, q->data)) { - log_warn(ckt, "failed to merge buffers"); - return -1; - } - p->length += q->length; - p->offset -= q->length; - p->flags |= q->flags; - - evbuffer_free(q->data); - q->next->prev = q->prev; - q->prev->next = q->next; - free(q); - } +// Protocol objects
- return 0; -} +struct chop_config_t; +struct chop_circuit_t;
-/* Flush as much data toward upstream as we can. */ -static int -chop_push_to_upstream(chop_circuit_t *ckt) -{ - /* Only the first reassembly queue entry, if any, can possibly be - ready to flush (because chop_reassemble_block ensures that there - are gaps between all queue elements). */ - chop_reassembly_elt *ready = ckt->reassembly_queue.next; - if (!ready->data || ckt->recv_offset != ready->offset) { - log_debug(ckt, "no data pushable to upstream yet"); - return 0; - } +typedef unordered_map<uint32_t, chop_circuit_t *> chop_circuit_table;
- if (!ckt->received_syn) { - if (!(ready->flags & CHOP_F_SYN)) { - log_debug(ckt, "waiting for SYN"); - return 0; - } - log_debug(ckt, "processed SYN"); - ckt->received_syn = true; - } +struct chop_conn_t : conn_t +{ + chop_config_t *config; + chop_circuit_t *upstream; + steg_t *steg; + struct evbuffer *recv_pending; + struct event *must_send_timer; + bool sent_handshake : 1; + bool no_more_transmissions : 1; + + CONN_DECLARE_METHODS(chop); + + int recv_handshake(); + int send(struct evbuffer *block); + + void send(); + bool must_send_p() const; + static void must_send_timeout(evutil_socket_t, short, void *arg); +};
- log_debug(ckt, "can push %lu bytes to upstream", - (unsigned long)evbuffer_get_length(ready->data)); - if (evbuffer_add_buffer(bufferevent_get_output(ckt->up_buffer), - ready->data)) { - log_warn(ckt, "failure pushing data to upstream"); - return -1; +struct chop_circuit_t : circuit_t +{ + reassembly_queue recv_queue; + unordered_set<chop_conn_t *> downstreams; + gcm_encryptor *send_crypt; + ecb_encryptor *send_hdr_crypt; + gcm_decryptor *recv_crypt; + ecb_decryptor *recv_hdr_crypt; + chop_config_t *config; + + uint32_t circuit_id; + uint32_t send_seq; + uint32_t dead_cycles; + bool received_fin : 1; + bool sent_fin : 1; + bool upstream_eof : 1; + + CIRCUIT_DECLARE_METHODS(chop); + + // Shortcut some unnecessary conversions for callers within this file. + void add_downstream(chop_conn_t *conn); + void drop_downstream(chop_conn_t *conn); + + int send_special(opcode_t f, struct evbuffer *payload); + int send_targeted(chop_conn_t *conn); + int send_targeted(chop_conn_t *conn, size_t blocksize); + int send_targeted(chop_conn_t *conn, size_t d, size_t p, opcode_t f, + struct evbuffer *payload); + + chop_conn_t *pick_connection(size_t desired, size_t *blocksize); + + int process_queue(); + int check_for_eof(); + + uint32_t axe_interval() { + // This function must always return a number which is larger than + // the maximum possible number that *our peer's* flush_interval() + // could have returned; otherwise, we might axe the connection when + // it was just that there was nothing to say for a while. + // For simplicity's sake, right now we hardwire this to be 30 minutes. + return 30 * 60 * 1000; } - - ckt->dead_cycles = 0; - ckt->recv_offset += ready->length; - - if (ready->flags & CHOP_F_FIN) { - log_assert(!ckt->received_fin); - log_assert(ready->next == &ckt->reassembly_queue); - ckt->received_fin = true; - log_debug(ckt, "processed FIN"); - circuit_recv_eof(ckt); + uint32_t flush_interval() { + // 10*60*1000 lies between 2^19 and 2^20. + uint32_t shift = std::max(1u, std::min(19u, dead_cycles)); + uint32_t xv = std::max(1u, std::min(10u * 60 * 1000, 1u << shift)); + return rng_range_geom(20 * 60 * 1000, xv) + 100; } +};
- log_assert(ready->next == &ckt->reassembly_queue || - ready->next->offset != ckt->recv_offset); - ready->next->prev = ready->prev; - ready->prev->next = ready->next; - - evbuffer_free(ready->data); - free(ready); - return 0; -} - -/* Circuit handling */ - -static int -chop_find_or_make_circuit(chop_conn_t *conn, uint64_t circuit_id) +struct chop_config_t : config_t { - chop_circuit_table::value_type in(circuit_id, 0); - std::pair<chop_circuit_table::iterator, bool> out - = conn->config->circuits.insert(in); - chop_circuit_t *ck; - - if (!out.second) { // element already exists - if (!out.first->second) { - log_debug(conn, "stale circuit"); - return 0; - } - ck = out.first->second; - log_debug(conn, "found circuit to %s", ck->up_peer); - } else { - ck = dynamic_cast<chop_circuit_t *>(circuit_create(conn->config, 0)); - if (!ck) { - log_warn(conn, "failed to create new circuit"); - return -1; - } - if (circuit_open_upstream(ck)) { - log_warn(conn, "failed to begin upstream connection"); - delete ck; - return -1; - } - log_debug(conn, "created new circuit to %s", ck->up_peer); - ck->circuit_id = circuit_id; - out.first->second = ck; - } + struct evutil_addrinfo *up_address; + vector<struct evutil_addrinfo *> down_addresses; + vector<const char *> steg_targets; + chop_circuit_table circuits;
- ck->add_downstream(conn); - return 0; -} + CONFIG_DECLARE_METHODS(chop); +};
-/* Protocol methods */ +// Configuration methods
chop_config_t::chop_config_t() { @@ -833,7 +390,7 @@ chop_config_t::~chop_config_t() i != down_addresses.end(); i++) evutil_freeaddrinfo(*i);
- /* The strings in steg_targets are not on the heap. */ + // The strings in steg_targets are not on the heap.
for (chop_circuit_table::iterator i = circuits.begin(); i != circuits.end(); i++) @@ -854,28 +411,28 @@ chop_config_t::init(int n_options, const char *const *options) }
if (!strcmp(options[0], "client")) { - defport = "48988"; /* bf5c */ - this->mode = LSN_SIMPLE_CLIENT; + defport = "48988"; // bf5c + mode = LSN_SIMPLE_CLIENT; listen_up = 1; } else if (!strcmp(options[0], "socks")) { - defport = "23548"; /* 5bf5 */ - this->mode = LSN_SOCKS_CLIENT; + defport = "23548"; // 5bf5 + mode = LSN_SOCKS_CLIENT; listen_up = 1; } else if (!strcmp(options[0], "server")) { - defport = "11253"; /* 2bf5 */ - this->mode = LSN_SIMPLE_SERVER; + defport = "11253"; // 2bf5 + mode = LSN_SIMPLE_SERVER; listen_up = 0; } else goto usage;
- this->up_address = resolve_address_port(options[1], 1, listen_up, defport); - if (!this->up_address) { + up_address = resolve_address_port(options[1], 1, listen_up, defport); + if (!up_address) { log_warn("chop: invalid up address: %s", options[1]); goto usage; }
- /* From here on out, arguments alternate between downstream - addresses and steg targets. */ + // From here on out, arguments alternate between downstream + // addresses and steg targets. for (i = 2; i < n_options; i++) { struct evutil_addrinfo *addr = resolve_address_port(options[i], 1, !listen_up, NULL); @@ -883,7 +440,7 @@ chop_config_t::init(int n_options, const char *const *options) log_warn("chop: invalid down address: %s", options[i]); goto usage; } - this->down_addresses.push_back(addr); + down_addresses.push_back(addr);
i++; if (i == n_options) { @@ -895,7 +452,7 @@ chop_config_t::init(int n_options, const char *const *options) log_warn("chop: steganographer '%s' not supported", options[i]); goto usage; } - this->steg_targets.push_back(options[i]); + steg_targets.push_back(options[i]); } return true;
@@ -917,12 +474,12 @@ chop_config_t::init(int n_options, const char *const *options) struct evutil_addrinfo * chop_config_t::get_listen_addrs(size_t n) { - if (this->mode == LSN_SIMPLE_SERVER) { - if (n < this->down_addresses.size()) - return this->down_addresses[n]; + if (mode == LSN_SIMPLE_SERVER) { + if (n < down_addresses.size()) + return down_addresses[n]; } else { if (n == 0) - return this->up_address; + return up_address; } return 0; } @@ -930,58 +487,96 @@ chop_config_t::get_listen_addrs(size_t n) struct evutil_addrinfo * chop_config_t::get_target_addrs(size_t n) { - if (this->mode == LSN_SIMPLE_SERVER) { + if (mode == LSN_SIMPLE_SERVER) { if (n == 0) - return this->up_address; + return up_address; } else { - if (n < this->down_addresses.size()) - return this->down_addresses[n]; + if (n < down_addresses.size()) + return down_addresses[n]; } return NULL; }
+// Circuit methods + +const char passphrase[] = + "did you buy one of therapist reawaken chemists continually gamma pacifies?"; + circuit_t * chop_config_t::circuit_create(size_t) { chop_circuit_t *ckt = new chop_circuit_t; ckt->config = this;
- if (this->mode == LSN_SIMPLE_SERVER) { - ckt->send_crypt = gcm_encryptor::create(s2c_key, 16); - ckt->recv_crypt = gcm_decryptor::create(c2s_key, 16); - } else { - ckt->send_crypt = gcm_encryptor::create(c2s_key, 16); - ckt->recv_crypt = gcm_decryptor::create(s2c_key, 16); - while (!ckt->circuit_id) - rng_bytes((uint8_t *)&ckt->circuit_id, sizeof(uint64_t)); - - chop_circuit_table::value_type in(ckt->circuit_id, 0); - std::pair<chop_circuit_table::iterator, bool> out = circuits.insert(in); - log_assert(out.second); - out.first->second = ckt; - } - return ckt; -} + key_generator *kgen = + key_generator::from_passphrase((const uint8_t *)passphrase, + sizeof(passphrase) - 1, + 0, 0, 0, 0); + uint8_t kbuf[16];
-chop_circuit_t::chop_circuit_t() -{ - this->reassembly_queue.next = &this->reassembly_queue; - this->reassembly_queue.prev = &this->reassembly_queue; + if (mode == LSN_SIMPLE_SERVER) { + log_assert(kgen->generate(kbuf, 16) == 16); + ckt->send_crypt = gcm_encryptor::create(kbuf, 16); + + log_assert(kgen->generate(kbuf, 16) == 16); + ckt->send_hdr_crypt = ecb_encryptor::create(kbuf, 16); + + log_assert(kgen->generate(kbuf, 16) == 16); + ckt->recv_crypt = gcm_decryptor::create(kbuf, 16); + + log_assert(kgen->generate(kbuf, 16) == 16); + ckt->recv_hdr_crypt = ecb_decryptor::create(kbuf, 16); + } else { + log_assert(kgen->generate(kbuf, 16) == 16); + ckt->recv_crypt = gcm_decryptor::create(kbuf, 16); + + log_assert(kgen->generate(kbuf, 16) == 16); + ckt->recv_hdr_crypt = ecb_decryptor::create(kbuf, 16); + + log_assert(kgen->generate(kbuf, 16) == 16); + ckt->send_crypt = gcm_encryptor::create(kbuf, 16); + + log_assert(kgen->generate(kbuf, 16) == 16); + ckt->send_hdr_crypt = ecb_encryptor::create(kbuf, 16); + + std::pair<chop_circuit_table::iterator, bool> out; + do { + do { + rng_bytes((uint8_t *)&ckt->circuit_id, sizeof(ckt->circuit_id)); + } while (!ckt->circuit_id); + + out = circuits.insert(make_pair(ckt->circuit_id, (chop_circuit_t *)0)); + } while (!out.second); + + out.first->second = ckt; + } + + memset(kbuf, 0, 16); + delete kgen; + return ckt; }
-chop_circuit_t::~chop_circuit_t() +chop_circuit_t::chop_circuit_t() { - chop_reassembly_elt *p, *q, *queue; - chop_circuit_table::iterator out; +}
- log_debug(this, "syn%c%c fin%c%c eof%c ds=%lu", - sent_syn ? '+' : '-', received_syn ? '+' : '-', - sent_fin ? '+' : '-', received_fin ? '+' : '-', - upstream_eof ? '+' : '-', - (unsigned long)downstreams.size()); +chop_circuit_t::~chop_circuit_t() +{ + if (!sent_fin || !received_fin || !upstream_eof) { + log_warn(this, "destroying active circuit: fin%c%c eof%c ds=%lu", + sent_fin ? '+' : '-', received_fin ? '+' : '-', + upstream_eof ? '+' : '-', + (unsigned long)downstreams.size()); +#ifdef HAVE_EXECINFO_H + int n; + void *backtracebuf[256]; + n = backtrace(backtracebuf, sizeof backtracebuf / sizeof(void*)); + backtrace_symbols_fd(backtracebuf, n, 2); +#endif + }
- for (unordered_set<chop_conn_t *>::iterator i = this->downstreams.begin(); - i != this->downstreams.end(); i++) { + for (unordered_set<chop_conn_t *>::iterator i = downstreams.begin(); + i != downstreams.end(); i++) { chop_conn_t *conn = *i; conn->upstream = NULL; if (evbuffer_get_length(conn->outbound()) > 0) @@ -990,28 +585,23 @@ chop_circuit_t::~chop_circuit_t() delete conn; }
- delete this->send_crypt; - delete this->recv_crypt; - - queue = &this->reassembly_queue; - for (q = p = queue->next; p != queue; p = q) { - q = p->next; - if (p->data) - evbuffer_free(p->data); - free(p); - } - - /* The IDs for old circuits are preserved for a while (at present, - indefinitely; FIXME: purge them on a timer) against the - possibility that we'll get a junk connection for one of them - right after we close it (same deal as the TIME_WAIT state in - TCP). Note that we can hit this case for the *client* if the - cover protocol includes a mandatory reply to every client - message and the hidden channel closed s->c before c->s: the - circuit will get destroyed on the client side after the c->s FIN, - and the mandatory reply will be to a stale circuit. */ - out = this->config->circuits.find(this->circuit_id); - log_assert(out != this->config->circuits.end()); + delete send_crypt; + delete send_hdr_crypt; + delete recv_crypt; + delete recv_hdr_crypt; + + // The IDs for old circuits are preserved for a while (at present, + // indefinitely; FIXME: purge them on a timer) against the + // possibility that we'll get a junk connection for one of them + // right after we close it (same deal as the TIME_WAIT state in + // TCP). Note that we can hit this case for the *client* if the + // cover protocol includes a mandatory reply to every client message + // and the hidden channel closed s->c before c->s: the circuit will + // get destroyed on the client side after the c->s FIN, and the + // mandatory reply will be to a stale circuit. + chop_circuit_table::iterator out; + out = config->circuits.find(circuit_id); + log_assert(out != config->circuits.end()); log_assert(out->second == this); out->second = NULL; } @@ -1019,69 +609,442 @@ chop_circuit_t::~chop_circuit_t() config_t * chop_circuit_t::cfg() const { - return this->config; + return config; }
void -chop_circuit_t::add_downstream(conn_t *cn) +chop_circuit_t::add_downstream(chop_conn_t *conn) { - chop_conn_t *conn = dynamic_cast<chop_conn_t *>(cn); log_assert(conn); log_assert(!conn->upstream); - conn->upstream = this; - this->downstreams.insert(conn); + downstreams.insert(conn);
log_debug(this, "added connection <%d.%d> to %s, now %lu", - this->serial, conn->serial, conn->peername, - (unsigned long)this->downstreams.size()); + serial, conn->serial, conn->peername, + (unsigned long)downstreams.size());
circuit_disarm_axe_timer(this); }
void -chop_circuit_t::drop_downstream(conn_t *cn) +chop_circuit_t::add_downstream(conn_t *cn) +{ + add_downstream(dynamic_cast<chop_conn_t *>(cn)); +} + +void +chop_circuit_t::drop_downstream(chop_conn_t *conn) { - chop_conn_t *conn = dynamic_cast<chop_conn_t *>(cn); log_assert(conn); log_assert(conn->upstream == this);
conn->upstream = NULL; - this->downstreams.erase(conn); + downstreams.erase(conn);
log_debug(this, "dropped connection <%d.%d> to %s, now %lu", - this->serial, conn->serial, conn->peername, - (unsigned long)this->downstreams.size()); - /* If that was the last connection on this circuit AND we've both - received and sent a FIN, close the circuit. Otherwise, if we're - the server, arm a timer that will kill off this circuit in a - little while if no new connections happen (we might've lost all - our connections to protocol errors, or because the steg modules - wanted them closed); if we're the client, send chaff in a bit, - to enable further transmissions from the server. */ - if (this->downstreams.empty()) { - if (this->sent_fin && this->received_fin) { - if (evbuffer_get_length(bufferevent_get_output(this->up_buffer)) > 0) - /* this may already have happened, but there's no harm in - doing it again */ + serial, conn->serial, conn->peername, + (unsigned long)downstreams.size()); + // If that was the last connection on this circuit AND we've both + // received and sent a FIN, close the circuit. Otherwise, if we're + // the server, arm a timer that will kill off this circuit in a + // little while if no new connections happen (we might've lost all + // our connections to protocol errors, or because the steg modules + // wanted them closed); if we're the client, send chaff in a bit, + // to enable further transmissions from the server. + if (downstreams.empty()) { + if (sent_fin && received_fin) { + if (evbuffer_get_length(bufferevent_get_output(up_buffer)) > 0) + // this may already have happened, but there's no harm in + // doing it again circuit_do_flush(this); else delete this; - } else if (this->config->mode == LSN_SIMPLE_SERVER) { - circuit_arm_axe_timer(this, this->axe_interval()); + } else if (config->mode == LSN_SIMPLE_SERVER) { + circuit_arm_axe_timer(this, axe_interval()); } else { - circuit_arm_flush_timer(this, this->flush_interval()); + circuit_arm_flush_timer(this, flush_interval()); + } + } +} + +void +chop_circuit_t::drop_downstream(conn_t *cn) +{ + drop_downstream(dynamic_cast<chop_conn_t *>(cn)); +} + +int +chop_circuit_t::send() +{ + circuit_disarm_flush_timer(this); + + if (downstreams.empty()) { + // We have no connections, but we must send. If we're the client, + // reopen our outbound connections; the on-connection event will + // bring us back here. If we're the server, we have to just + // twiddle our thumbs and hope the client reconnects. + log_debug(this, "no downstream connections"); + if (config->mode != LSN_SIMPLE_SERVER) + circuit_reopen_downstreams(this); + else + circuit_arm_axe_timer(this, axe_interval()); + return 0; + } + + struct evbuffer *xmit_pending = bufferevent_get_input(up_buffer); + size_t avail = evbuffer_get_length(xmit_pending); + size_t avail0 = avail; + + // Send at least one block, even if there is no real data to send. + do { + log_debug(this, "%lu bytes to send", (unsigned long)avail); + size_t blocksize; + chop_conn_t *target = pick_connection(avail, &blocksize); + if (!target) { + // this is not an error; it can happen e.g. when the server has + // something to send immediately and the client hasn't spoken yet + log_debug(this, "no target connection available"); + break; + } + + if (send_targeted(target, blocksize)) + return -1; + + avail = evbuffer_get_length(xmit_pending); + } while (avail > 0); + + if (avail0 > avail) // we transmitted some real data + dead_cycles = 0; + else { + dead_cycles++; + log_debug(this, "%u dead cycles", dead_cycles); + } + + return check_for_eof(); +} + +int +chop_circuit_t::send_eof() +{ + upstream_eof = true; + return send(); +} + +int +chop_circuit_t::send_special(opcode_t f, struct evbuffer *payload) +{ + size_t d = payload ? evbuffer_get_length(payload) : 0; + size_t blocksize; + log_assert(d <= SECTION_LEN); + chop_conn_t *conn = pick_connection(d, &blocksize); + + if (!conn || (blocksize - MIN_BLOCK_SIZE < d)) { + log_warn("no usable connection for special block " + "(opcode %02x, need %lu bytes, have %lu)", + (unsigned int)f, (unsigned long)(d + MIN_BLOCK_SIZE), + (unsigned long)blocksize); + return -1; + } + + return send_targeted(conn, d, (blocksize - MIN_BLOCK_SIZE) - d, f, payload); +} + +int +chop_circuit_t::send_targeted(chop_conn_t *conn) +{ + size_t avail = evbuffer_get_length(bufferevent_get_input(up_buffer)); + if (avail > SECTION_LEN) + avail = SECTION_LEN; + avail += MIN_BLOCK_SIZE; + + size_t room = conn->steg->transmit_room(conn); + if (room < MIN_BLOCK_SIZE) { + log_warn(conn, "send() called without enough transmit room " + "(have %lu, need %lu)", (unsigned long)room, + (unsigned long)MIN_BLOCK_SIZE); + return -1; + } + log_debug(conn, "offers %lu bytes (%s)", (unsigned long)room, + conn->steg->name()); + + if (room < avail) + avail = room; + + return send_targeted(conn, avail); +} + +int +chop_circuit_t::send_targeted(chop_conn_t *conn, size_t blocksize) +{ + log_assert(blocksize >= MIN_BLOCK_SIZE && blocksize <= MAX_BLOCK_SIZE); + + struct evbuffer *xmit_pending = bufferevent_get_input(up_buffer); + size_t avail = evbuffer_get_length(xmit_pending); + opcode_t op = op_DAT; + + if (avail > SECTION_LEN) + avail = SECTION_LEN; + else if (upstream_eof && !sent_fin) + // this block will carry the last byte of real data to be sent in + // this direction; mark it as such + op = op_FIN; + + return send_targeted(conn, avail, (blocksize - MIN_BLOCK_SIZE) - avail, + op, xmit_pending); +} + +int +chop_circuit_t::send_targeted(chop_conn_t *conn, size_t d, size_t p, opcode_t f, + struct evbuffer *payload) +{ + log_assert(payload || d == 0); + log_assert(d <= SECTION_LEN); + log_assert(p <= SECTION_LEN); + + struct evbuffer *block = evbuffer_new(); + if (!block) { + log_warn(conn, "memory allocation failure"); + return -1; + } + + size_t blocksize = d + p + MIN_BLOCK_SIZE; + struct evbuffer_iovec v; + if (evbuffer_reserve_space(block, blocksize, &v, 1) != 1 || + v.iov_len < blocksize) { + log_warn(conn, "memory allocation failure"); + return -1; + } + v.iov_len = blocksize; + + block_header hdr(send_seq, d, p, f, *send_hdr_crypt); + log_assert(hdr.valid(send_seq)); + memcpy(v.iov_base, hdr.nonce(), HEADER_LEN); + + uint8_t encodebuf[SECTION_LEN*2]; + if (payload) { + if (evbuffer_copyout(payload, encodebuf, d) != (ssize_t)d) { + log_warn(conn, "failed to extract payload"); + evbuffer_free(block); + return -1; + } + } + memset(encodebuf + d, 0, p); + send_crypt->encrypt((uint8_t *)v.iov_base + HEADER_LEN, encodebuf, + d + p, hdr.nonce(), HEADER_LEN); + if (evbuffer_commit_space(block, &v, 1)) { + log_warn(conn, "failed to commit block buffer"); + evbuffer_free(block); + return -1; + } + + log_debug(conn, "transmitting block %u <d=%lu p=%lu f=%02x>", + hdr.seqno(), (unsigned long)hdr.dlen(), (unsigned long)hdr.plen(), + (uint8_t)hdr.opcode()); + + if (conn->send(block)) { + evbuffer_free(block); + return -1; + } + + evbuffer_free(block); + evbuffer_drain(payload, d); + + send_seq++; + if (f == op_FIN) + sent_fin = true; + return 0; +} + +// N.B. 'desired' is the desired size of the _data section_, and +// 'blocksize' on output is the size to make the _entire block_. +chop_conn_t * +chop_circuit_t::pick_connection(size_t desired, size_t *blocksize) +{ + size_t maxbelow = 0; + size_t minabove = MAX_BLOCK_SIZE + 1; + chop_conn_t *targbelow = 0; + chop_conn_t *targabove = 0; + + if (desired > SECTION_LEN) + desired = SECTION_LEN; + + desired += MIN_BLOCK_SIZE; + + log_debug(this, "target block size %lu bytes", (unsigned long)desired); + + // Find the best fit for the desired transmission from all the + // outbound connections' transmit rooms. + for (unordered_set<chop_conn_t *>::iterator i = downstreams.begin(); + i != downstreams.end(); i++) { + chop_conn_t *conn = *i; + // We can only use candidates that have a steg target already. + if (conn->steg) { + // Find the connections whose transmit rooms are closest to the + // desired transmission length from both directions. + size_t room = conn->steg->transmit_room(conn); + + if (room <= MIN_BLOCK_SIZE) + room = 0; + + if (room > MAX_BLOCK_SIZE) + room = MAX_BLOCK_SIZE; + + log_debug(conn, "offers %lu bytes (%s)", (unsigned long)room, + conn->steg->name()); + + if (room >= desired) { + if (room < minabove) { + minabove = room; + targabove = conn; + } + } else { + if (room > maxbelow) { + maxbelow = room; + targbelow = conn; + } + } + } else { + log_debug(conn, "offers 0 bytes (no steg)"); + } + } + + log_debug(this, "minabove %lu for <%u.%u> maxbelow %lu for <%u.%u>", + (unsigned long)minabove, serial, targabove ? targabove->serial :0, + (unsigned long)maxbelow, serial, targbelow ? targbelow->serial :0); + + // If we have a connection that can take all the data, use it. + // Otherwise, use the connection that can take as much of the data + // as possible. As a special case, if no connection can take data, + // targbelow, targabove, maxbelow, and minabove will all still have + // their initial values, so we'll return NULL and set blocksize to 0, + // which callers know how to handle. + if (targabove) { + *blocksize = desired; + return targabove; + } else { + *blocksize = maxbelow; + return targbelow; + } +} + +int +chop_circuit_t::process_queue() +{ + reassembly_elt blk; + unsigned int count = 0; + bool pending_fin = false; + bool pending_error = false; + bool sent_error = false; + while ((blk = recv_queue.remove_next()).data) { + switch (blk.op) { + case op_FIN: + if (received_fin) { + log_info(this, "protocol error: duplicate FIN"); + pending_error = true; + break; + } + log_debug(this, "received FIN"); + pending_fin = true; + // fall through - block may have data + case op_DAT: + if (evbuffer_get_length(blk.data)) { + if (received_fin) { + log_info(this, "protocol error: data after FIN"); + pending_error = true; + } else { + if (evbuffer_add_buffer(bufferevent_get_output(up_buffer), + blk.data)) { + log_warn(this, "buffer transfer failure"); + pending_error = true; + } + } + } + break; + + case op_RST: + log_info(this, "received RST; disconnecting circuit"); + circuit_recv_eof(this); + pending_error = true; + break; + + case op_RK1: + case op_RK2: + case op_RK3: + log_warn(this, "rekeying not yet implemented"); + pending_error = true; + break; + + default: + log_warn(this, "protocol error: unknown block opcode %x", + (unsigned int)blk.op); + pending_error = true; + break; + } + + evbuffer_free(blk.data); + + if (pending_fin && !received_fin) { + circuit_recv_eof(this); + received_fin = true; } + if (pending_error && !sent_error) { + // there's no point sending an RST in response to an RST or a + // duplicate FIN + if (blk.op != op_RST && blk.op != op_FIN) + send_special(op_RST, 0); + sent_error = true; + } + count++; } + + log_debug(this, "processed %u blocks", count); + if (count > 0) + dead_cycles = 0; + if (sent_error) + return -1; + + // It may have become possible to send queued data or a FIN. + if (evbuffer_get_length(bufferevent_get_input(up_buffer)) + || (upstream_eof && !sent_fin)) + return send(); + + return check_for_eof(); }
+int +chop_circuit_t::check_for_eof() +{ + // If we're at EOF both ways, close all connections, sending first + // if necessary. + if (sent_fin && received_fin) { + circuit_disarm_flush_timer(this); + for (unordered_set<chop_conn_t *>::iterator i = downstreams.begin(); + i != downstreams.end(); i++) { + chop_conn_t *conn = *i; + if (conn->must_send_p()) + conn->send(); + conn_send_eof(conn); + } + } + + + // If we're the client we have to keep trying to talk as long as we + // haven't both sent and received a FIN, or we might deadlock. + else if (config->mode != LSN_SIMPLE_SERVER) + circuit_arm_flush_timer(this, flush_interval()); + + return 0; +} + +// Connection methods + conn_t * chop_config_t::conn_create(size_t index) { chop_conn_t *conn = new chop_conn_t; conn->config = this; - conn->steg = steg_new(this->steg_targets.at(index), - this->mode != LSN_SIMPLE_SERVER); + conn->steg = steg_new(steg_targets.at(index), mode != LSN_SIMPLE_SERVER); if (!conn->steg) { free(conn); return 0; @@ -1097,271 +1060,239 @@ chop_conn_t::chop_conn_t()
chop_conn_t::~chop_conn_t() { - if (this->upstream) - this->upstream->drop_downstream(this); - if (this->steg) - delete this->steg; - if (this->must_transmit_timer) - event_free(this->must_transmit_timer); - evbuffer_free(this->recv_pending); + if (upstream) + upstream->drop_downstream(this); + if (steg) + delete steg; + if (must_send_timer) + event_free(must_send_timer); + evbuffer_free(recv_pending); }
circuit_t * chop_conn_t::circuit() const { - return this->upstream; + return upstream; }
int chop_conn_t::maybe_open_upstream() { - /* We can't open the upstream until we have a circuit ID. */ + // We can't open the upstream until we have a circuit ID. + return 0; +} + +int +chop_conn_t::send(struct evbuffer *block) +{ + if (!sent_handshake && config->mode != LSN_SIMPLE_SERVER) { + if (!upstream || upstream->circuit_id == 0) + log_abort(this, "handshake: can't happen: up%c cid=%u", + upstream ? '+' : '-', + upstream ? upstream->circuit_id : 0); + if (evbuffer_prepend(block, (void *)&upstream->circuit_id, + sizeof(upstream->circuit_id))) { + log_warn(this, "failed to prepend handshake to first block"); + return -1; + } + } + + if (steg->transmit(block, this)) { + log_warn(this, "failed to transmit block"); + return -1; + } + sent_handshake = true; + if (must_send_timer) + evtimer_del(must_send_timer); return 0; }
int chop_conn_t::handshake() { - /* Chop has no handshake as such, but like dsteg, we need to send - _something_ from the client on at least one of the channels - shortly after connection, because the server doesn't know which - connections go with which circuits till it hears from us, _and_ - it doesn't know what steganography to use. We use a 1ms timeout - instead of a 10ms timeout as in dsteg, because unlike there, the - server can't even _connect to its upstream_ till it gets the - first packet from the client. */ - if (this->config->mode != LSN_SIMPLE_SERVER) - circuit_arm_flush_timer(this->upstream, 1); + // The actual handshake is generated in chop_conn_t::send so that it + // can be merged with a block if possible; however, we use this hook + // to ensure that the client sends _something_ ASAP after each new + // connection, because the server can't forward traffic, or even + // open a socket to its own upstream, until it knows which circuit + // to associate this new connection with. Note that in some cases + // it's possible for us to have _already_ sent something on this + // connection by the time we get called back! Don't do it twice. + if (config->mode != LSN_SIMPLE_SERVER && !sent_handshake) + send(); return 0; }
int -chop_circuit_t::send() +chop_conn_t::recv_handshake() { - circuit_disarm_flush_timer(this); + log_assert(!upstream); + log_assert(config->mode == LSN_SIMPLE_SERVER);
- if (this->downstreams.empty()) { - /* We have no connections, but we must send. If we're the client, - reopen our outbound connections; the on-connection event will - bring us back here. If we're the server, we have to just - twiddle our thumbs and hope the client reconnects. */ - log_debug(this, "no downstream connections"); - if (this->config->mode != LSN_SIMPLE_SERVER) - circuit_reopen_downstreams(this); - else - circuit_arm_axe_timer(this, this->axe_interval()); - return 0; - } + uint32_t circuit_id; + if (evbuffer_remove(recv_pending, (void *)&circuit_id, + sizeof circuit_id) != sizeof circuit_id) + return -1;
- if (evbuffer_get_length(bufferevent_get_input(this->up_buffer)) == 0) { - /* must-send timer expired and we still have nothing to say; send chaff */ - if (chop_send_chaff(this)) - return -1; - this->dead_cycles++; - log_debug(this, "%u dead cycles", this->dead_cycles); - } else { - if (chop_send_blocks(this)) - return -1; - this->dead_cycles = 0; - } + chop_circuit_table::value_type in(circuit_id, 0); + std::pair<chop_circuit_table::iterator, bool> out + = this->config->circuits.insert(in); + chop_circuit_t *ck;
- /* If we're at EOF, close all connections (sending first if - necessary). If we're the client we have to keep trying to talk - as long as we haven't both sent and received a FIN, or we might - deadlock. */ - if (this->sent_fin && this->received_fin) { - for (unordered_set<chop_conn_t *>::iterator i = this->downstreams.begin(); - i != this->downstreams.end(); i++) { - chop_conn_t *conn = *i; - if (conn->must_transmit_timer && - evtimer_pending(conn->must_transmit_timer, NULL)) - must_transmit_timer_cb(-1, 0, conn); - conn_send_eof(conn); + if (!out.second) { // element already exists + if (!out.first->second) { + log_debug(this, "stale circuit"); + return 0; } + ck = out.first->second; + log_debug(this, "found circuit to %s", ck->up_peer); } else { - if (this->config->mode != LSN_SIMPLE_SERVER) - circuit_arm_flush_timer(this, this->flush_interval()); + ck = dynamic_cast<chop_circuit_t *>(circuit_create(this->config, 0)); + if (!ck) { + log_warn(this, "failed to create new circuit"); + return -1; + } + if (circuit_open_upstream(ck)) { + log_warn(this, "failed to begin upstream connection"); + delete ck; + return -1; + } + log_debug(this, "created new circuit to %s", ck->up_peer); + ck->circuit_id = circuit_id; + out.first->second = ck; } - return 0; -}
-int -chop_circuit_t::send_eof() -{ - this->upstream_eof = true; - return this->send(); + ck->add_downstream(this); + return 0; }
int chop_conn_t::recv() { - chop_circuit_t *ckt; - chop_header hdr; - struct evbuffer *block; - size_t avail; - uint8_t decodebuf[CHOP_MAX_DATA + CHOP_WIRE_HDR_LEN]; - - if (this->steg->receive(this, this->recv_pending)) + if (steg->receive(this, recv_pending)) return -1;
- if (!this->upstream) { - log_debug(this, "finding circuit"); - if (chop_peek_circuit_id(this->recv_pending, &hdr)) { - log_debug(this, "not enough data to find circuit yet"); - return 0; - } - if (chop_find_or_make_circuit(this, hdr.ckt_id)) + if (!upstream) { + // Try to receive a handshake. + if (recv_handshake()) return -1; - /* If we get here and this->circuit is not set, this is a connection - for a stale circuit: that is, a new connection made by the - client (to draw more data down from the server) that crossed - with a server-to-client FIN. We can't decrypt the packet, but - it's either chaff or a protocol error; either way we can just - discard it. Since we will never reply, call conn_do_flush so - the connection will be dropped as soon as we receive an EOF. */ - if (!this->upstream) { - evbuffer_drain(this->recv_pending, - evbuffer_get_length(this->recv_pending)); + + // If we get here and ->upstream is not set, this is a connection + // for a stale circuit: that is, a new connection made by the + // client (to draw more data down from the server) that crossed + // with a server-to-client FIN, the client-to-server FIN already + // having been received and processed. We no longer have the keys + // to decrypt anything after the handshake, but it's either chaff + // or a protocol error. Either way, we can just drop the + // connection, possibly sending a response if the cover protocol + // requires one. + if (!upstream) { + evbuffer_drain(recv_pending, evbuffer_get_length(recv_pending)); + if (must_send_p()) + send(); conn_do_flush(this); return 0; } }
- ckt = this->upstream; - log_debug(this, "circuit to %s", ckt->up_peer); - + log_debug(this, "circuit to %s", upstream->up_peer); for (;;) { - avail = evbuffer_get_length(this->recv_pending); + size_t avail = evbuffer_get_length(recv_pending); if (avail == 0) break;
log_debug(this, "%lu bytes available", (unsigned long)avail); - if (avail < CHOP_WIRE_HDR_LEN) { - log_debug(this, "incomplete block"); + if (avail < MIN_BLOCK_SIZE) { + log_debug(this, "incomplete block framing"); break; }
- if (chop_decrypt_header(ckt, this->recv_pending, &hdr)) + block_header hdr(recv_pending, *upstream->recv_hdr_crypt); + if (!hdr.valid(upstream->recv_queue.window())) { + const uint8_t *c = hdr.cleartext(); + log_warn(this, "invalid block header: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], + c[8], c[9], c[10], c[11], c[12], c[13], c[14], c[15]); return -1; - - if (avail < CHOP_WIRE_HDR_LEN + GCM_TAG_LEN + hdr.length) { + } + if (avail < hdr.total_len()) { log_debug(this, "incomplete block (need %lu bytes)", - (unsigned long)(CHOP_WIRE_HDR_LEN + GCM_TAG_LEN + hdr.length)); + (unsigned long)hdr.total_len()); break; }
- if (ckt->circuit_id != hdr.ckt_id) { - log_warn(this, "protocol error: circuit id mismatch"); - return -1; - } - - log_debug(this, "receiving block of %lu+%u bytes " - "[offset %u flags %04hx]", - (unsigned long)CHOP_WIRE_HDR_LEN + GCM_TAG_LEN, - hdr.length, hdr.offset, hdr.flags); - - if (evbuffer_copyout(this->recv_pending, decodebuf, - CHOP_WIRE_HDR_LEN + GCM_TAG_LEN + hdr.length) - != (ssize_t)(CHOP_WIRE_HDR_LEN + GCM_TAG_LEN + hdr.length)) { + uint8_t decodebuf[MAX_BLOCK_SIZE]; + if (evbuffer_drain(recv_pending, HEADER_LEN) || + evbuffer_remove(recv_pending, decodebuf, hdr.total_len() - HEADER_LEN) + != (ssize_t)(hdr.total_len() - HEADER_LEN)) { log_warn(this, "failed to copy block to decode buffer"); return -1; } - block = evbuffer_new(); - if (!block || evbuffer_expand(block, hdr.length)) { - log_warn(this, "allocation failure"); + if (upstream->recv_crypt->decrypt(decodebuf, + decodebuf, hdr.total_len() - HEADER_LEN, + hdr.nonce(), HEADER_LEN)) { + log_warn("MAC verification failure"); return -1; }
- if (ckt->recv_crypt - ->decrypt(decodebuf + 16, decodebuf + 16, - hdr.length + CHOP_WIRE_HDR_LEN + GCM_TAG_LEN - 16, - decodebuf, 16)) { - log_warn(this, "MAC verification failure"); - evbuffer_free(block); - return -1; - } - - if (evbuffer_add(block, decodebuf + CHOP_WIRE_HDR_LEN, hdr.length)) { - log_warn(this, "failed to transfer block to reassembly queue"); - evbuffer_free(block); - return -1; - } - - if (evbuffer_drain(this->recv_pending, - CHOP_WIRE_HDR_LEN + GCM_TAG_LEN + hdr.length)) { - log_warn(this, "failed to consume block from wire"); - evbuffer_free(block); - return -1; - } + log_debug(this, "receiving block %u <d=%lu p=%lu f=%u>", + hdr.seqno(), (unsigned long)hdr.dlen(), (unsigned long)hdr.plen(), + (unsigned int)hdr.opcode());
- if (chop_reassemble_block(ckt, block, &hdr)) { - evbuffer_free(block); + evbuffer *data = evbuffer_new(); + if (!data || (hdr.dlen() && evbuffer_add(data, decodebuf, hdr.dlen()))) { + log_warn(this, "failed to extract data from decode buffer"); + evbuffer_free(data); return -1; } - } - - if (chop_push_to_upstream(ckt)) - return -1;
- /* It may have now become possible to send queued data. */ - if (evbuffer_get_length(bufferevent_get_input(ckt->up_buffer))) - ckt->send(); - - /* If we're at EOF, close all connections (sending first if - necessary). If we're the client we have to keep trying to talk - as long as we haven't both sent and received a FIN, or we might - deadlock. */ - else if (ckt->sent_fin && ckt->received_fin) { - circuit_disarm_flush_timer(ckt); - for (unordered_set<chop_conn_t *>::iterator i = ckt->downstreams.begin(); - i != ckt->downstreams.end(); i++) { - chop_conn_t *conn = *i; - if (conn->must_transmit_timer && - evtimer_pending(conn->must_transmit_timer, NULL)) - must_transmit_timer_cb(-1, 0, conn); - conn_send_eof(conn); - } - } else { - if (ckt->config->mode != LSN_SIMPLE_SERVER) - circuit_arm_flush_timer(ckt, ckt->flush_interval()); + if (!upstream->recv_queue.insert(hdr.seqno(), hdr.opcode(), data, this)) + return -1; // insert() logs an error }
- return 0; + return upstream->process_queue(); }
int chop_conn_t::recv_eof() { - /* EOF on a _connection_ does not mean EOF on a _circuit_. - EOF on a _circuit_ occurs when chop_push_to_upstream processes a FIN. - We should only drop the connection from the circuit if we're no - longer sending in the opposite direction. Also, we should not - drop the connection if its must-transmit timer is still pending. */ - if (this->upstream) { - chop_circuit_t *ckt = this->upstream; - - if (evbuffer_get_length(this->inbound()) > 0) - if (this->recv()) - return -1; - - if ((ckt->sent_fin || this->no_more_transmissions) && - (!this->must_transmit_timer || - !evtimer_pending(this->must_transmit_timer, NULL))) - ckt->drop_downstream(this); + // Consume any not-yet-processed incoming data. It's possible for + // us to get here before we've processed _any_ data -- including the + // handshake! -- from a new connection, so we have to do this before + // we look at ->upstream. */ + if (evbuffer_get_length(inbound()) > 0) { + if (recv()) + return -1; + // If there's anything left in the buffer at this point, it's a + // protocol error. + if (evbuffer_get_length(inbound()) > 0) + return -1; } + + // We should only drop the connection from the circuit if we're no + // longer sending covert data in the opposite direction _and_ the + // cover protocol does not need us to send a reply (i.e. the + // must_send_timer is not pending). + if (upstream && (upstream->sent_fin || no_more_transmissions) && + !must_send_p()) + upstream->drop_downstream(this); + return 0; }
void chop_conn_t::expect_close() { - /* do we need to do something here? */ + // We currently don't need to do anything here. + // FIXME: figure out if this hook is _ever_ useful, and if not, remove it. }
void chop_conn_t::cease_transmission() { - this->no_more_transmissions = true; + no_more_transmissions = true; + if (must_send_timer) + evtimer_del(must_send_timer); conn_do_flush(this); }
@@ -1370,13 +1301,97 @@ chop_conn_t::transmit_soon(unsigned long milliseconds) { struct timeval tv;
- log_debug(this, "must transmit within %lu milliseconds", milliseconds); + log_debug(this, "must send within %lu milliseconds", milliseconds);
tv.tv_sec = milliseconds / 1000; tv.tv_usec = (milliseconds % 1000) * 1000;
- if (!this->must_transmit_timer) - this->must_transmit_timer = evtimer_new(this->config->base, - must_transmit_timer_cb, this); - evtimer_add(this->must_transmit_timer, &tv); + if (!must_send_timer) + must_send_timer = evtimer_new(config->base, must_send_timeout, this); + evtimer_add(must_send_timer, &tv); +} + +void +chop_conn_t::send() +{ + if (must_send_timer) + evtimer_del(must_send_timer); + + if (!steg) { + log_warn(this, "send() called with no steg module available"); + conn_do_flush(this); + return; + } + + // When this happens, we must send _even if_ we have no upstream to + // provide us with data. For instance, to preserve the cover + // protocol, we must send an HTTP reply to each HTTP query that + // comes in for a stale circuit. + if (upstream) { + log_debug(this, "must send"); + if (upstream->send_targeted(this)) + conn_do_flush(this); + + } else { + log_debug(this, "must send (no upstream)"); + + size_t room = steg->transmit_room(this); + if (room < MIN_BLOCK_SIZE) { + log_warn(this, "send() called without enough transmit room " + "(have %lu, need %lu)", (unsigned long)room, + (unsigned long)MIN_BLOCK_SIZE); + conn_do_flush(this); + return; + } + + // Since we have no upstream, we can't encrypt anything; instead, + // generate random bytes and feed them straight to steg_transmit. + struct evbuffer *chaff = evbuffer_new(); + struct evbuffer_iovec v; + if (!chaff || evbuffer_reserve_space(chaff, MIN_BLOCK_SIZE, &v, 1) != 1 || + v.iov_len < MIN_BLOCK_SIZE) { + log_warn(this, "memory allocation failed"); + if (chaff) + evbuffer_free(chaff); + conn_do_flush(this); + return; + } + v.iov_len = MIN_BLOCK_SIZE; + rng_bytes((uint8_t *)v.iov_base, MIN_BLOCK_SIZE); + if (evbuffer_commit_space(chaff, &v, 1)) { + log_warn(this, "evbuffer_commit_space failed"); + if (chaff) + evbuffer_free(chaff); + conn_do_flush(this); + return; + } + + if (steg->transmit(chaff, this)) + conn_do_flush(this); + + evbuffer_free(chaff); + } +} + +bool +chop_conn_t::must_send_p() const +{ + return must_send_timer && evtimer_pending(must_send_timer, 0); +} + +/* static */ void +chop_conn_t::must_send_timeout(evutil_socket_t, short, void *arg) +{ + static_cast<chop_conn_t *>(arg)->send(); } + +} // anonymous namespace + +PROTO_DEFINE_MODULE(chop); + +// Local Variables: +// mode: c++ +// c-basic-offset: 2 +// c-file-style: "gnu" +// c-file-offsets: ((innamespace . 0) (brace-list-open . 0)) +// End:
tor-commits@lists.torproject.org