diff --git a/src/core/or/circuitlist.c b/src/core/or/circuitlist.c
index 91a02ce56f..72952a8a52 100644
--- a/src/core/or/circuitlist.c
+++ b/src/core/or/circuitlist.c
@@ -2195,6 +2195,11 @@ circuit_mark_for_close_, (circuit_t *circ, int reason, int line,
tor_assert(line);
tor_assert(file);
+ /* Check whether the circuitpadding subsystem wants to block this close */
+ if (circpad_marked_circuit_for_padding(circ, reason)) {
+ return;
+ }
+
if (circ->marked_for_close) {
log_warn(LD_BUG,
"Duplicate call to circuit_mark_for_close at %s:%d"
diff --git a/src/core/or/circuitpadding.c b/src/core/or/circuitpadding.c
index 58e8e053c7..68140356c9 100644
--- a/src/core/or/circuitpadding.c
+++ b/src/core/or/circuitpadding.c
@@ -163,6 +163,116 @@ circpad_circuit_machineinfo_free_idx(circuit_t *circ, int idx)
}
}
+/**
+ * Return true if circpad has decided to hold the circuit open for additional
+ * padding. This function is used to take and retain ownership of certain
+ * types of circuits that have padding machines on them, that have been passed
+ * to circuit_mark_for_close().
+ *
+ * circuit_mark_for_close() calls this function to ask circpad if any padding
+ * machines want to keep the circuit open longer to pad.
+ *
+ * Any non-measurement circuit that was closed for a normal, non-error reason
+ * code may be held open for up to CIRCPAD_DELAY_INFINITE microseconds between
+ * network-driven cell events.
+ *
+ * After CIRCPAD_DELAY_INFINITE microseconds of silence on a circuit, this
+ * function will no longer hold it open (it will return 0 regardless of
+ * what the machines ask for, and thus circuit_expire_old_circuits_clientside()
+ * will close the circuit after roughly 1.25hr of idle time, maximum,
+ * regardless of the padding machine state.
+ */
+int
+circpad_marked_circuit_for_padding(circuit_t *circ, int reason)
+{
+ /* If the circuit purpose is measurement or path bias, don't
+ * hold it open */
+ if (circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING ||
+ circ->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
+ return 0;
+ }
+
+ /* If the circuit is closed for any reason other than these three valid,
+ * client-side close reasons, do not try to keep it open. It is probably
+ * damaged or unusable. Note this is OK with vanguards because
+ * controller-closed circuits have REASON=REQUESTED, so vanguards-closed
+ * circuits will not be held open (we want them to close ASAP). */
+ if (!(reason == END_CIRC_REASON_NONE ||
+ reason == END_CIRC_REASON_FINISHED ||
+ reason == END_CIRC_REASON_IP_NOW_REDUNDANT)) {
+ return 0;
+ }
+
+ FOR_EACH_ACTIVE_CIRCUIT_MACHINE_BEGIN(i, circ) {
+ circpad_machine_runtime_t *mi = circ->padding_info[i];
+ if (!mi) {
+ continue; // No padding runtime info; check next machine
+ }
+
+ const circpad_state_t *state = circpad_machine_current_state(mi);
+
+ /* If we're in END state (NULL here), then check next machine */
+ if (!state) {
+ continue; // check next machine
+ }
+
+ /* If the machine does not want to control the circuit close itself, then
+ * check the next machine */
+ if (!circ->padding_machine[i]->manage_circ_lifetime) {
+ continue; // check next machine
+ }
+
+ /* If the machine has reached the END state, we can close. Check next
+ * machine. */
+ if (mi->current_state == CIRCPAD_STATE_END) {
+ continue; // check next machine
+ }
+
+ log_info(LD_CIRC, "Circuit %d is not marked for close because of a "
+ " pending padding machine.", CIRCUIT_IS_ORIGIN(circ) ?
+ TO_ORIGIN_CIRCUIT(circ)->global_identifier : 0);
+
+ /* If the machine has had no network events at all within the
+ * last circpad_delay_t timespan, it's in some deadlock state.
+ * Tell circuit_mark_for_close() that we don't own it anymore.
+ * This will allow circuit_expire_old_circuits_clientside() to
+ * close it.
+ */
+ if (circ->padding_info[i]->last_cell_time_sec +
+ (time_t)CIRCPAD_DELAY_MAX_SECS < approx_time()) {
+ log_notice(LD_BUG, "Circuit %d was not marked for close because of a "
+ " pending padding machine for over an hour. Circuit is a %s",
+ CIRCUIT_IS_ORIGIN(circ) ?
+ TO_ORIGIN_CIRCUIT(circ)->global_identifier : 0,
+ circuit_purpose_to_string(circ->purpose));
+
+ return 0; // abort timer reached; mark the circuit for close now
+ }
+
+ /* If we weren't marked dirty yet, let's pretend we're dirty now.
+ * ("Dirty" means that a circuit has been used for application traffic
+ * by Tor.. Dirty circuits have different expiry times, and are not
+ * considered in counts of built circuits, etc. By claiming that we're
+ * dirty, the rest of Tor will make decisions as if we were actually
+ * used by application data.
+ *
+ * This is most important for circuit_expire_old_circuits_clientside(),
+ * where we want that function to expire us after the padding machine
+ * has shut down, but using the MaxCircuitDirtiness timer instead of
+ * the idle circuit timer (again, we want this because we're not
+ * supposed to look idle to Guard nodes that can see our lifespan). */
+ if (!circ->timestamp_dirty)
+ circ->timestamp_dirty = approx_time();
+
+ /* Take ownership of the circuit */
+ circuit_change_purpose(circ, CIRCUIT_PURPOSE_C_CIRCUIT_PADDING);
+
+ return 1;
+ } FOR_EACH_ACTIVE_CIRCUIT_MACHINE_END;
+
+ return 0; // No machine wanted to keep the circuit open; mark for close
+}
+
/** Free all the machineinfos in circ that match machine_num. */
static void
free_circ_machineinfos_with_machine_num(circuit_t *circ, int machine_num)
@@ -197,6 +307,7 @@ circpad_circuit_machineinfo_new(circuit_t *on_circ, int machine_index)
tor_malloc_zero(sizeof(circpad_machine_runtime_t));
mi->machine_index = machine_index;
mi->on_circ = on_circ;
+ mi->last_cell_time_sec = approx_time();
return mi;
}
@@ -1538,7 +1649,8 @@ circpad_cell_event_nonpadding_sent(circuit_t *on_circ)
/* If there are no machines then this loop should not iterate */
FOR_EACH_ACTIVE_CIRCUIT_MACHINE_BEGIN(i, on_circ) {
- /* First, update any RTT estimate */
+ /* First, update any timestamps */
+ on_circ->padding_info[i]->last_cell_time_sec = approx_time();
circpad_estimate_circ_rtt_on_send(on_circ, on_circ->padding_info[i]);
/* Remove a token: this is the idea of adaptive padding, since we have an
@@ -1564,7 +1676,8 @@ void
circpad_cell_event_nonpadding_received(circuit_t *on_circ)
{
FOR_EACH_ACTIVE_CIRCUIT_MACHINE_BEGIN(i, on_circ) {
- /* First, update any RTT estimate */
+ /* First, update any timestamps */
+ on_circ->padding_info[i]->last_cell_time_sec = approx_time();
circpad_estimate_circ_rtt_on_received(on_circ, on_circ->padding_info[i]);
circpad_machine_spec_transition(on_circ->padding_info[i],
@@ -1584,6 +1697,7 @@ void
circpad_cell_event_padding_sent(circuit_t *on_circ)
{
FOR_EACH_ACTIVE_CIRCUIT_MACHINE_BEGIN(i, on_circ) {
+ on_circ->padding_info[i]->last_cell_time_sec = approx_time();
circpad_machine_spec_transition(on_circ->padding_info[i],
CIRCPAD_EVENT_PADDING_SENT);
} FOR_EACH_ACTIVE_CIRCUIT_MACHINE_END;
@@ -1602,6 +1716,7 @@ circpad_cell_event_padding_received(circuit_t *on_circ)
{
/* identical to padding sent */
FOR_EACH_ACTIVE_CIRCUIT_MACHINE_BEGIN(i, on_circ) {
+ on_circ->padding_info[i]->last_cell_time_sec = approx_time();
circpad_machine_spec_transition(on_circ->padding_info[i],
CIRCPAD_EVENT_PADDING_RECV);
} FOR_EACH_ACTIVE_CIRCUIT_MACHINE_END;
diff --git a/src/core/or/circuitpadding.h b/src/core/or/circuitpadding.h
index f00369eb0a..56ed0a3467 100644
--- a/src/core/or/circuitpadding.h
+++ b/src/core/or/circuitpadding.h
@@ -73,6 +73,7 @@ typedef uint64_t circpad_time_t;
/** The type for timer delays, in microseconds */
typedef uint32_t circpad_delay_t;
+#define CIRCPAD_DELAY_UNITS_PER_SECOND (1000*1000)
/**
* An infinite padding cell delay means don't schedule any padding --
@@ -85,6 +86,13 @@ typedef uint32_t circpad_delay_t;
*/
#define CIRCPAD_DELAY_INFINITE (UINT32_MAX)
+/**
+ * This is the maximum delay that the circuit padding system can have, in
+ * seconds.
+ */
+#define CIRCPAD_DELAY_MAX_SECS \
+ ((CIRCPAD_DELAY_INFINITE/CIRCPAD_DELAY_UNITS_PER_SECOND)+1)
+
/**
* Macro to clarify when we're checking the infinity bin.
*
@@ -524,6 +532,14 @@ typedef struct circpad_machine_runtime_t {
* half. */
uint16_t nonpadding_sent;
+ /**
+ * Timestamp of the most recent cell event (sent, received, padding,
+ * non-padding), in seconds from approx_time().
+ *
+ * Used as an emergency break to stop holding padding circuits open.
+ */
+ time_t last_cell_time_sec;
+
/**
* EWMA estimate of the RTT of the circuit from this hop
* to the exit end, in microseconds. */
@@ -605,6 +621,19 @@ typedef struct circpad_machine_spec_t {
* 1-indexed (ie: hop #1 is guard, #2 middle, #3 exit). */
unsigned target_hopnum : 3;
+ /** If this flag is enabled, don't close circuits that use this machine even
+ * if another part of Tor wants to close this circuit.
+ *
+ * If this flag is set, the circuitpadding subsystem will close circuits the
+ * moment the machine transitions to the END state, and only if the circuit
+ * has already been asked to be closed by another part of Tor.
+ *
+ * Circuits that should have been closed but were kept open by a padding
+ * machine are re-purposed to CIRCUIT_PURPOSE_C_CIRCUIT_PADDING, hence
+ * machines should take that purpose into account if they are filtering
+ * circuits by purpose. */
+ unsigned manage_circ_lifetime : 1;
+
/** This machine only kills fascists if the following conditions are met. */
circpad_machine_conditions_t conditions;
@@ -630,6 +659,8 @@ typedef struct circpad_machine_spec_t {
void circpad_new_consensus_params(const networkstatus_t *ns);
+int circpad_marked_circuit_for_padding(circuit_t *circ, int reason);
+
/**
* The following are event call-in points that are of interest to
* the state machines. They are called during cell processing. */