[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stegotorus/master] Revise chop.cc to be closer to what the paper says it does:
commit 49302db0cd095bbc02c631e1628542f4d8b003a9
Author: Zack Weinberg <zackw@xxxxxxxxx>
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 mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits