Browse Source

Merge branch 'bug25903_v5_squashed'

Nick Mathewson 6 years ago
parent
commit
a394a2dd86
10 changed files with 377 additions and 11 deletions
  1. 6 0
      changes/ticket25903
  2. 38 0
      src/or/circuituse.c
  3. 2 0
      src/or/circuituse.h
  4. 8 1
      src/or/connection_edge.c
  5. 12 3
      src/or/control.c
  6. 22 2
      src/or/or.h
  7. 40 5
      src/or/relay.c
  8. 4 0
      src/or/relay.h
  9. 6 0
      src/or/rendcommon.c
  10. 239 0
      src/test/test_relaycell.c

+ 6 - 0
changes/ticket25903

@@ -0,0 +1,6 @@
+  o Minor features (control port):
+    - Introduce new fields to the CIRC_BW event. There are two new fields in
+      each of the read and written directions. The DELIVERED fields report the
+      total valid data on the circuit, as measured by the payload sizes of
+      verified and error-checked relay command cells. The OVERHEAD fields
+      report the total unused bytes in each of these cells. Closes ticket 25903.

+ 38 - 0
src/or/circuituse.c

@@ -3106,3 +3106,41 @@ mark_circuit_unusable_for_new_conns(origin_circuit_t *circ)
   circ->unusable_for_new_conns = 1;
 }
 
+/**
+ * Add relay_body_len and RELAY_PAYLOAD_SIZE-relay_body_len to
+ * the valid delivered written fields and the overhead field,
+ * respectively.
+ */
+void
+circuit_sent_valid_data(origin_circuit_t *circ, uint16_t relay_body_len)
+{
+  if (!circ) return;
+
+  tor_assert_nonfatal(relay_body_len <= RELAY_PAYLOAD_SIZE);
+
+  circ->n_delivered_written_circ_bw =
+      tor_add_u32_nowrap(circ->n_delivered_written_circ_bw, relay_body_len);
+  circ->n_overhead_written_circ_bw =
+      tor_add_u32_nowrap(circ->n_overhead_written_circ_bw,
+                         RELAY_PAYLOAD_SIZE-relay_body_len);
+}
+
+/**
+ * Add relay_body_len and RELAY_PAYLOAD_SIZE-relay_body_len to
+ * the valid delivered read field and the overhead field,
+ * respectively.
+ */
+void
+circuit_read_valid_data(origin_circuit_t *circ, uint16_t relay_body_len)
+{
+  if (!circ) return;
+
+  tor_assert_nonfatal(relay_body_len <= RELAY_PAYLOAD_SIZE);
+
+  circ->n_delivered_read_circ_bw =
+      tor_add_u32_nowrap(circ->n_delivered_read_circ_bw, relay_body_len);
+  circ->n_overhead_read_circ_bw =
+      tor_add_u32_nowrap(circ->n_overhead_read_circ_bw,
+                         RELAY_PAYLOAD_SIZE-relay_body_len);
+}
+

+ 2 - 0
src/or/circuituse.h

@@ -65,6 +65,8 @@ void mark_circuit_unusable_for_new_conns(origin_circuit_t *circ);
 
 int circuit_purpose_is_hidden_service(uint8_t);
 int circuit_should_use_vanguards(uint8_t);
+void circuit_sent_valid_data(origin_circuit_t *circ, uint16_t relay_body_len);
+void circuit_read_valid_data(origin_circuit_t *circ, uint16_t relay_body_len);
 
 #ifdef TOR_UNIT_TESTS
 /* Used only by circuituse.c and test_circuituse.c */

+ 8 - 1
src/or/connection_edge.c

@@ -3527,10 +3527,17 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
   n_stream->deliver_window = STREAMWINDOW_START;
 
   if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) {
+    int ret;
     tor_free(address);
     /* We handle this circuit and stream in this function for all supported
      * hidden service version. */
-    return handle_hs_exit_conn(circ, n_stream);
+    ret = handle_hs_exit_conn(circ, n_stream);
+
+    if (ret == 0) {
+      /* This was a valid cell. Count it as delivered + overhead. */
+      circuit_read_valid_data(origin_circ, rh.length);
+    }
+    return ret;
   }
   tor_strlower(address);
   n_stream->base_.address = address;

+ 12 - 3
src/or/control.c

@@ -265,6 +265,8 @@ clear_circ_bw_fields(void)
       continue;
     ocirc = TO_ORIGIN_CIRCUIT(circ);
     ocirc->n_written_circ_bw = ocirc->n_read_circ_bw = 0;
+    ocirc->n_overhead_written_circ_bw = ocirc->n_overhead_read_circ_bw = 0;
+    ocirc->n_delivered_written_circ_bw = ocirc->n_delivered_read_circ_bw = 0;
   }
   SMARTLIST_FOREACH_END(circ);
 }
@@ -5990,13 +5992,20 @@ control_event_circ_bandwidth_used(void)
     tor_gettimeofday(&now);
     format_iso_time_nospace_usec(tbuf, &now);
     send_control_event(EVENT_CIRC_BANDWIDTH_USED,
-                       "650 CIRC_BW ID=%d READ=%lu WRITTEN=%lu "
-                       "TIME=%s\r\n",
+                       "650 CIRC_BW ID=%d READ=%lu WRITTEN=%lu TIME=%s "
+                       "DELIVERED_READ=%lu OVERHEAD_READ=%lu "
+                       "DELIVERED_WRITTEN=%lu OVERHEAD_WRITTEN=%lu\r\n",
                        ocirc->global_identifier,
                        (unsigned long)ocirc->n_read_circ_bw,
                        (unsigned long)ocirc->n_written_circ_bw,
-                       tbuf);
+                       tbuf,
+                       (unsigned long)ocirc->n_delivered_read_circ_bw,
+                       (unsigned long)ocirc->n_overhead_read_circ_bw,
+                       (unsigned long)ocirc->n_delivered_written_circ_bw,
+                       (unsigned long)ocirc->n_overhead_written_circ_bw);
     ocirc->n_written_circ_bw = ocirc->n_read_circ_bw = 0;
+    ocirc->n_overhead_written_circ_bw = ocirc->n_overhead_read_circ_bw = 0;
+    ocirc->n_delivered_written_circ_bw = ocirc->n_delivered_read_circ_bw = 0;
   }
   SMARTLIST_FOREACH_END(circ);
 

+ 22 - 2
src/or/or.h

@@ -3260,16 +3260,36 @@ typedef struct origin_circuit_t {
    * associated with this circuit. */
   edge_connection_t *p_streams;
 
-  /** Bytes read from any attached stream since last call to
+  /** Bytes read on this circuit since last call to
    * control_event_circ_bandwidth_used().  Only used if we're configured
    * to emit CIRC_BW events. */
   uint32_t n_read_circ_bw;
 
-  /** Bytes written to any attached stream since last call to
+  /** Bytes written to on this circuit since last call to
    * control_event_circ_bandwidth_used().  Only used if we're configured
    * to emit CIRC_BW events. */
   uint32_t n_written_circ_bw;
 
+  /** Total known-valid relay cell bytes since last call to
+   * control_event_circ_bandwidth_used().  Only used if we're configured
+   * to emit CIRC_BW events. */
+  uint32_t n_delivered_read_circ_bw;
+
+  /** Total written relay cell bytes since last call to
+   * control_event_circ_bandwidth_used().  Only used if we're configured
+   * to emit CIRC_BW events. */
+  uint32_t n_delivered_written_circ_bw;
+
+  /** Total overhead data in all known-valid relay data cells since last
+   * call to control_event_circ_bandwidth_used().  Only used if we're
+   * configured to emit CIRC_BW events. */
+  uint32_t n_overhead_read_circ_bw;
+
+  /** Total written overhead data in all relay data cells since last call to
+   * control_event_circ_bandwidth_used().  Only used if we're configured
+   * to emit CIRC_BW events. */
+  uint32_t n_overhead_written_circ_bw;
+
   /** Build state for this circuit. It includes the intended path
    * length, the chosen exit router, rendezvous information, etc.
    */

+ 40 - 5
src/or/relay.c

@@ -85,9 +85,6 @@ static edge_connection_t *relay_lookup_conn(circuit_t *circ, cell_t *cell,
                                             cell_direction_t cell_direction,
                                             crypt_path_t *layer_hint);
 
-static int connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
-                                              edge_connection_t *conn,
-                                              crypt_path_t *layer_hint);
 static void circuit_consider_sending_sendme(circuit_t *circ,
                                             crypt_path_t *layer_hint);
 static void circuit_resume_edge_reading(circuit_t *circ,
@@ -613,6 +610,10 @@ relay_send_command_from_edge_,(streamid_t stream_id, circuit_t *circ,
       tor_free(commands);
       smartlist_free(commands_list);
     }
+
+    /* Let's assume we're well-behaved: Anything that we decide to send is
+     * valid, delivered data. */
+    circuit_sent_valid_data(origin_circ, rh.length);
   }
 
   if (circuit_package_relay_cell(&cell, circ, cell_direction, cpath_layer,
@@ -742,6 +743,9 @@ connection_ap_process_end_not_open(
     }
   }
 
+  /* This end cell is now valid. */
+  circuit_read_valid_data(circ, rh->length);
+
   if (rh->length == 0) {
     reason = END_STREAM_REASON_MISC;
   }
@@ -1232,6 +1236,12 @@ connection_edge_process_resolved_cell(edge_connection_t *conn,
     }
   }
 
+  /* This is valid data at this point. Count it */
+  if (conn->on_circuit && CIRCUIT_IS_ORIGIN(conn->on_circuit)) {
+    circuit_read_valid_data(TO_ORIGIN_CIRCUIT(conn->on_circuit),
+                            rh->length);
+  }
+
   connection_ap_handshake_socks_got_resolved_cell(entry_conn,
                                                   errcode,
                                                   resolved_addresses);
@@ -1328,6 +1338,9 @@ connection_edge_process_relay_cell_not_open(
                                 entry_conn->chosen_exit_name, ttl);
 
       remap_event_helper(entry_conn, &addr);
+
+      /* This is valid data at this point. Count it */
+      circuit_read_valid_data(TO_ORIGIN_CIRCUIT(circ), rh->length);
     }
     circuit_log_path(LOG_INFO,LD_APP,TO_ORIGIN_CIRCUIT(circ));
     /* don't send a socks reply to transparent conns */
@@ -1398,7 +1411,7 @@ connection_edge_process_relay_cell_not_open(
  *
  * Return -reason if you want to warn and tear down the circuit, else 0.
  */
-static int
+STATIC int
 connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
                                    edge_connection_t *conn,
                                    crypt_path_t *layer_hint)
@@ -1498,7 +1511,6 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
         circ->dirreq_id = ++next_id;
         TO_OR_CIRCUIT(circ)->p_chan->dirreq_id = circ->dirreq_id;
       }
-
       return connection_exit_begin_conn(cell, circ);
     case RELAY_COMMAND_DATA:
       ++stats_n_data_cells_received;
@@ -1534,6 +1546,10 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
                "(relay data) conn deliver_window below 0. Killing.");
         return -END_CIRC_REASON_TORPROTOCOL;
       }
+      /* Total all valid application bytes delivered */
+      if (CIRCUIT_IS_ORIGIN(circ)) {
+        circuit_read_valid_data(TO_ORIGIN_CIRCUIT(circ), rh.length);
+      }
 
       stats_n_data_bytes_received += rh.length;
       connection_buf_add((char*)(cell->payload + RELAY_HEADER_SIZE),
@@ -1586,6 +1602,11 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
         /* only mark it if not already marked. it's possible to
          * get the 'end' right around when the client hangs up on us. */
         connection_mark_and_flush(TO_CONN(conn));
+
+        /* Total all valid application bytes delivered */
+        if (CIRCUIT_IS_ORIGIN(circ)) {
+          circuit_read_valid_data(TO_ORIGIN_CIRCUIT(circ), rh.length);
+        }
       }
       return 0;
     case RELAY_COMMAND_EXTEND:
@@ -1651,6 +1672,10 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
         log_info(domain,"circuit_send_next_onion_skin() failed.");
         return reason;
       }
+      /* Total all valid bytes delivered. */
+      if (CIRCUIT_IS_ORIGIN(circ)) {
+        circuit_read_valid_data(TO_ORIGIN_CIRCUIT(circ), rh.length);
+      }
       return 0;
     case RELAY_COMMAND_TRUNCATE:
       if (layer_hint) {
@@ -1716,6 +1741,16 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
           log_debug(LD_APP,"circ-level sendme at origin, packagewindow %d.",
                     layer_hint->package_window);
           circuit_resume_edge_reading(circ, layer_hint);
+
+          /* We count circuit-level sendme's as valid delivered data because
+           * they are rate limited. Note that we cannot count stream
+           * sendme's because the other end could send as many as they like.
+           */
+          if (CIRCUIT_IS_ORIGIN(circ)) {
+            circuit_read_valid_data(TO_ORIGIN_CIRCUIT(circ),
+                                    rh.length);
+          }
+
         } else {
           if (circ->package_window + CIRCWINDOW_INCREMENT >
                 CIRCWINDOW_START_MAX) {

+ 4 - 0
src/or/relay.h

@@ -114,6 +114,10 @@ STATIC packed_cell_t *packed_cell_new(void);
 STATIC packed_cell_t *cell_queue_pop(cell_queue_t *queue);
 STATIC destroy_cell_t *destroy_cell_queue_pop(destroy_cell_queue_t *queue);
 STATIC int cell_queues_check_size(void);
+STATIC int connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ,
+                                   edge_connection_t *conn,
+                                   crypt_path_t *layer_hint);
+
 #endif /* defined(RELAY_PRIVATE) */
 
 #endif /* !defined(TOR_RELAY_H) */

+ 6 - 0
src/or/rendcommon.c

@@ -12,6 +12,7 @@
 
 #include "or.h"
 #include "circuitbuild.h"
+#include "circuituse.h"
 #include "config.h"
 #include "control.h"
 #include "crypto_rand.h"
@@ -809,6 +810,11 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
       tor_fragile_assert();
   }
 
+  if (r == 0 && origin_circ) {
+    /* This was a valid cell. Count it as delivered + overhead. */
+    circuit_read_valid_data(origin_circ, length);
+  }
+
   if (r == -2)
     log_info(LD_PROTOCOL, "Dropping cell (type %d) for wrong circuit type.",
              command);

+ 239 - 0
src/test/test_relaycell.c

@@ -4,9 +4,14 @@
 /* Unit tests for handling different kinds of relay cell */
 
 #define RELAY_PRIVATE
+#define CIRCUITLIST_PRIVATE
 #include "or.h"
+#include "main.h"
 #include "config.h"
 #include "connection.h"
+#include "crypto.h"
+#include "circuitbuild.h"
+#include "circuitlist.h"
 #include "connection_edge.h"
 #include "relay.h"
 #include "test.h"
@@ -20,6 +25,11 @@ static uint8_t srm_answer[512];
 static int srm_ttl;
 static time_t srm_expires;
 
+void connection_free_minimal(connection_t*);
+int connected_cell_format_payload(uint8_t *payload_out,
+                              const tor_addr_t *addr,
+                              uint32_t ttl);
+
 /* Mock replacement for connection_ap_hannshake_socks_resolved() */
 static void
 socks_resolved_mock(entry_connection_t *conn,
@@ -60,6 +70,234 @@ mark_unattached_mock(entry_connection_t *conn, int endreason,
   (void) file;
 }
 
+/* Helper: Return a newly allocated and initialized origin circuit with
+ * purpose and flags. A default HS identifier is set to an ed25519
+ * authentication key for introduction point. */
+static origin_circuit_t *
+helper_create_origin_circuit(int purpose, int flags)
+{
+  origin_circuit_t *circ = NULL;
+
+  circ = origin_circuit_init(purpose, flags);
+  tor_assert(circ);
+  circ->cpath = tor_malloc_zero(sizeof(crypt_path_t));
+  circ->cpath->magic = CRYPT_PATH_MAGIC;
+  circ->cpath->state = CPATH_STATE_OPEN;
+  circ->cpath->package_window = circuit_initial_package_window();
+  circ->cpath->deliver_window = CIRCWINDOW_START;
+  circ->cpath->prev = circ->cpath;
+  /* Create a default HS identifier. */
+  circ->hs_ident = tor_malloc_zero(sizeof(hs_ident_circuit_t));
+
+  return circ;
+}
+
+static void
+mock_connection_mark_unattached_ap_(entry_connection_t *conn, int endreason,
+                                    int line, const char *file)
+{
+  (void) line;
+  (void) file;
+  conn->edge_.end_reason = endreason;
+}
+
+static void
+mock_mark_for_close(connection_t *conn,
+                        int line, const char *file)
+{
+  (void)line;
+  (void)file;
+
+  conn->marked_for_close = 1;
+  return;
+}
+
+static void
+mock_start_reading(connection_t *conn)
+{
+  (void)conn;
+  return;
+}
+
+static void
+test_circbw_relay(void *arg)
+{
+  cell_t cell;
+  relay_header_t rh;
+  tor_addr_t addr;
+  edge_connection_t *edgeconn;
+  entry_connection_t *entryconn;
+  origin_circuit_t *circ;
+  int delivered = 0;
+  int overhead = 0;
+
+  (void)arg;
+
+#define PACK_CELL(id, cmd, body_s) do {                                  \
+    memset(&cell, 0, sizeof(cell));                                     \
+    memset(&rh, 0, sizeof(rh));                                         \
+    memcpy(cell.payload+RELAY_HEADER_SIZE, (body_s), sizeof((body_s))-1); \
+    rh.length = sizeof((body_s))-1;                                     \
+    rh.command = (cmd);                                                 \
+    rh.stream_id = (id);                                                \
+    relay_header_pack((uint8_t*)&cell.payload, &rh);                    \
+  } while (0)
+#define ASSERT_COUNTED_BW() do { \
+    tt_int_op(circ->n_delivered_read_circ_bw, OP_EQ, delivered+rh.length); \
+    tt_int_op(circ->n_overhead_read_circ_bw, OP_EQ,                      \
+              overhead+RELAY_PAYLOAD_SIZE-rh.length);               \
+    delivered = circ->n_delivered_read_circ_bw;                          \
+    overhead = circ->n_overhead_read_circ_bw;                            \
+ } while (0)
+#define ASSERT_UNCOUNTED_BW() do { \
+    tt_int_op(circ->n_delivered_read_circ_bw, OP_EQ, delivered); \
+    tt_int_op(circ->n_overhead_read_circ_bw, OP_EQ, overhead); \
+ } while (0)
+
+  MOCK(connection_mark_unattached_ap_, mock_connection_mark_unattached_ap_);
+  MOCK(connection_start_reading, mock_start_reading);
+  MOCK(connection_mark_for_close_internal_, mock_mark_for_close);
+
+  entryconn = entry_connection_new(CONN_TYPE_AP, AF_INET);
+  edgeconn = ENTRY_TO_EDGE_CONN(entryconn);
+  edgeconn->base_.state = AP_CONN_STATE_CONNECT_WAIT;
+  edgeconn->deliver_window = 1000;
+  circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_C_GENERAL, 0);
+  edgeconn->cpath_layer = circ->cpath;
+  circ->cpath->state = CPATH_STATE_AWAITING_KEYS;
+  circ->cpath->deliver_window = 1000;
+
+  /* Stream id 0: Not counted */
+  PACK_CELL(0, RELAY_COMMAND_END, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), edgeconn,
+                                     circ->cpath);
+  ASSERT_UNCOUNTED_BW();
+
+  /* Stream id 1: Counted */
+  PACK_CELL(1, RELAY_COMMAND_END, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), edgeconn,
+                                     circ->cpath);
+  ASSERT_COUNTED_BW();
+
+  /* Properly formatted connect cell: counted */
+  PACK_CELL(1, RELAY_COMMAND_CONNECTED, "Data1234");
+  tor_addr_parse(&addr, "30.40.50.60");
+  rh.length = connected_cell_format_payload(cell.payload+RELAY_HEADER_SIZE,
+                                            &addr, 1024);
+  relay_header_pack((uint8_t*)&cell.payload, &rh);                    \
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), edgeconn,
+                                     circ->cpath);
+  ASSERT_COUNTED_BW();
+
+  /* Properly formatted resolved cell in correct state: counted */
+  edgeconn->base_.state = AP_CONN_STATE_RESOLVE_WAIT;
+  entryconn->socks_request->command = SOCKS_COMMAND_RESOLVE;
+  edgeconn->on_circuit = TO_CIRCUIT(circ);
+  PACK_CELL(1, RELAY_COMMAND_RESOLVED,
+            "\x04\x04\x12\x00\x00\x01\x00\x00\x02\x00");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), edgeconn,
+                                     circ->cpath);
+  ASSERT_COUNTED_BW();
+
+  edgeconn->base_.state = AP_CONN_STATE_OPEN;
+  entryconn->socks_request->has_finished = 1;
+
+  /* Connected cell after open: not counted */
+  PACK_CELL(1, RELAY_COMMAND_CONNECTED, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), edgeconn,
+                                     circ->cpath);
+  ASSERT_UNCOUNTED_BW();
+
+  /* Resolved cell after open: not counted */
+  PACK_CELL(1, RELAY_COMMAND_RESOLVED, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), edgeconn,
+                                     circ->cpath);
+  ASSERT_UNCOUNTED_BW();
+
+  /* Drop cell: not counted */
+  PACK_CELL(1, RELAY_COMMAND_DROP, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), edgeconn,
+                                     circ->cpath);
+  ASSERT_UNCOUNTED_BW();
+
+  /* Data cell on stream 0: not counted */
+  PACK_CELL(0, RELAY_COMMAND_DATA, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), edgeconn,
+                                     circ->cpath);
+  ASSERT_UNCOUNTED_BW();
+
+  /* Data cell on open connection: counted */
+  ENTRY_TO_CONN(entryconn)->marked_for_close = 0;
+  PACK_CELL(1, RELAY_COMMAND_DATA, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), edgeconn,
+                                     circ->cpath);
+  ASSERT_COUNTED_BW();
+
+  /* Sendme on stream: not counted */
+  ENTRY_TO_CONN(entryconn)->outbuf_flushlen = 0;
+  PACK_CELL(1, RELAY_COMMAND_SENDME, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), edgeconn,
+                                     circ->cpath);
+  ASSERT_UNCOUNTED_BW();
+
+  /* Sendme on circuit with full window: not counted */
+  PACK_CELL(0, RELAY_COMMAND_SENDME, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), edgeconn,
+                                     circ->cpath);
+  ASSERT_UNCOUNTED_BW();
+
+  /* Sendme on circuit with non-full window: counted */
+  PACK_CELL(0, RELAY_COMMAND_SENDME, "Data1234");
+  circ->cpath->package_window = 900;
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), edgeconn,
+                                     circ->cpath);
+  ASSERT_COUNTED_BW();
+
+  /* End cell on non-closed connection: counted */
+  PACK_CELL(1, RELAY_COMMAND_END, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), edgeconn,
+                                     circ->cpath);
+  ASSERT_COUNTED_BW();
+
+  /* End cell on connection that already got one: not counted */
+  PACK_CELL(1, RELAY_COMMAND_END, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), NULL,
+                                     circ->cpath);
+  ASSERT_UNCOUNTED_BW();
+
+  /* Invalid extended cell: not counted */
+  PACK_CELL(1, RELAY_COMMAND_EXTENDED2, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), NULL,
+                                     circ->cpath);
+  ASSERT_UNCOUNTED_BW();
+
+  /* Invalid extended cell: not counted */
+  PACK_CELL(1, RELAY_COMMAND_EXTENDED, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), NULL,
+                                     circ->cpath);
+  ASSERT_UNCOUNTED_BW();
+
+  /* Invalid HS cell: not counted */
+  PACK_CELL(1, RELAY_COMMAND_ESTABLISH_INTRO, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), NULL,
+                                     circ->cpath);
+  ASSERT_UNCOUNTED_BW();
+
+  /* "Valid" HS cell in expected state: counted */
+  TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_C_ESTABLISH_REND;
+  PACK_CELL(1, RELAY_COMMAND_RENDEZVOUS_ESTABLISHED, "Data1234");
+  connection_edge_process_relay_cell(&cell, TO_CIRCUIT(circ), NULL,
+                                     circ->cpath);
+  ASSERT_COUNTED_BW();
+
+ done:
+  UNMOCK(connection_start_reading);
+  UNMOCK(connection_mark_unattached_ap_);
+  UNMOCK(connection_mark_for_close_internal_);
+  circuit_free_(TO_CIRCUIT(circ));
+  connection_free_minimal(ENTRY_TO_CONN(entryconn));
+}
+
 /* Tests for connection_edge_process_resolved_cell().
 
    The point of ..process_resolved_cell() is to handle an incoming cell
@@ -244,6 +482,7 @@ test_relaycell_resolved(void *arg)
 
 struct testcase_t relaycell_tests[] = {
   { "resolved", test_relaycell_resolved, TT_FORK, NULL, NULL },
+  { "circbw", test_circbw_relay, TT_FORK, NULL, NULL },
   END_OF_TESTCASES
 };