[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [metrics-tasks/master] Add bridge stability report draft (#4255).
commit 85a545e66a8ba85d512e30b955c28c1b90e915ea
Author: Karsten Loesing <karsten.loesing@xxxxxxx>
Date: Sun Oct 30 07:11:56 2011 +0100
Add bridge stability report draft (#4255).
---
task-4255/.gitignore | 11 +
task-4255/README | 29 ++
task-4255/SimulateBridgeStability.java | 770 ++++++++++++++++++++++++++++++++
task-4255/report.bib | 26 ++
task-4255/report.tex | 414 +++++++++++++++++
task-4255/stability.R | 220 +++++++++
task-4255/stale-bridge-tarballs.csv | 760 +++++++++++++++++++++++++++++++
7 files changed, 2230 insertions(+), 0 deletions(-)
diff --git a/task-4255/.gitignore b/task-4255/.gitignore
new file mode 100644
index 0000000..4251d3d
--- /dev/null
+++ b/task-4255/.gitignore
@@ -0,0 +1,11 @@
+*.pdf
+*.log
+stability.csv
+stable-fingerprints-and-addresses
+*.class
+*.aux
+*.bbl
+*.blg
+bridge-statuses/
+future-stability/
+
diff --git a/task-4255/README b/task-4255/README
new file mode 100644
index 0000000..7ff10c9
--- /dev/null
+++ b/task-4255/README
@@ -0,0 +1,29 @@
+Extract sanitized bridge network statuses (not server descriptors or
+extra-info descriptors) to bridge-statuses/, e.g.
+bridge-statuses/bridge-descriptors-2010-01/statuses/. Leaving server or
+extra-info descriptors in the directory may lead to errors or at least
+delay the evaluation significantly.
+
+Compile the Java class:
+
+ $ javac SimulateBridgeStability.java
+
+Run the Java class:
+
+ $ java SimulateBridgeStability
+
+Before re-running, delete the following files/directories to re-generate
+them:
+
+ - stable-fingerprints-and-addresses
+ - future-stability/
+ - stability.csv
+
+Plot the results:
+
+ $ R --slave -f stability.R
+
+Compile the report:
+
+ $ pdflatex report.tex
+
diff --git a/task-4255/SimulateBridgeStability.java b/task-4255/SimulateBridgeStability.java
new file mode 100644
index 0000000..6deb318
--- /dev/null
+++ b/task-4255/SimulateBridgeStability.java
@@ -0,0 +1,770 @@
+import java.io.*;
+import java.text.*;
+import java.util.*;
+
+/**
+ * Simulate different requirements for stable bridges that BridgeDB can
+ * use to include at least one such stable bridge in its responses to
+ * bridge users.
+ *
+ * The two bridge stability metrics used here are weighted mean time
+ * between address changes (WMTBAC) and weighted fractional uptime (WFU).
+ * Different requirements based on these two metrics are simulated by
+ * comparing the time on same address (TOSA) and weighted fractional
+ * uptime in the future (WFU).
+ */
+public class SimulateBridgeStability {
+
+ /* Bridge history entry for the third step. Once we teach BridgeDB
+ * how to keep track of bridge stability, it's going to keep records
+ * similar to this one. */
+ private static class BridgeHistoryElement {
+ /* Weighted uptime in seconds that is used for WFU calculation. */
+ public long weightedUptime;
+ /* Weighted time in seconds that is used for WFU calculation. */
+ public long weightedTime;
+ /* Weighted run length of previously used addresses or ports in
+ * seconds. */
+ public double weightedRunLength;
+ /* Total run weights of previously used addresses or ports. */
+ public double totalRunWeights;
+ /* Currently known address. */
+ public String address;
+ /* Month string (YYYY-MM) that was used as input to the bridge
+ * descriptor sanitizer. */
+ public String month;
+ /* Currently known port. */
+ public int port;
+ /* Timestamp in milliseconds when this bridge was last seen with a
+ * different address or port. When adding a history entry, this
+ * timestamp is initialized with the publication time of the previous
+ * status. */
+ public long lastSeenWithDifferentAddressAndPort;
+ /* Timestamp in milliseconds when this bridge was last seen with this
+ * address and port. */
+ public long lastSeenWithThisAddressAndPort;
+ }
+
+ /* Run the analysis in three steps:
+ *
+ * In the first step, we parse sanitized bridge network statuses from
+ * first to last to determine stable addresses that have changed by the
+ * sanitizing process only. In the second step, we parse statuses from
+ * last to first to calculate TOSA and future WFU, and write future
+ * stability metrics to disk as one file per network status in
+ * future-stability/$filename. In the third step, we parse the statuses
+ * again from first to last, calculate past stability metrics for all
+ * bridges, select stable bridges, look up future stability of these
+ * bridges, and write results to stability.csv.
+ */
+ public static void main(String[] args) throws Exception {
+
+ /* Measure how long this execution takes. */
+ long started = System.currentTimeMillis();
+
+ /* Prepare timestamp parsing. */
+ SimpleDateFormat isoFormat = new SimpleDateFormat(
+ "yyyy-MM-dd HH:mm:ss");
+ isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ SimpleDateFormat statusFileNameFormat = new SimpleDateFormat(
+ "yyyyMMdd-HHmmss");
+ statusFileNameFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ SimpleDateFormat futureStabilityFileNameFormat = new SimpleDateFormat(
+ "yyyy-MM-dd-HH-mm-ss");
+ futureStabilityFileNameFormat.setTimeZone(TimeZone.getTimeZone(
+ "UTC"));
+
+ /* Define analysis interval. */
+ String analyzeFrom = "2010-07-01 00:00:00",
+ analyzeTo = "2011-06-30 23:00:00";
+ long analyzeFromMillis = isoFormat.parse(analyzeFrom).getTime(),
+ analyzeToMillis = isoFormat.parse(analyzeTo).getTime();
+
+ /* Scan existing status files. */
+ SortedSet<File> allStatuses = new TreeSet<File>();
+ Stack<File> files = new Stack<File>();
+ files.add(new File("bridge-statuses"));
+ while (!files.isEmpty()) {
+ File file = files.pop();
+ if (file.isDirectory()) {
+ files.addAll(Arrays.asList(file.listFiles()));
+ } else {
+ if (file.getName().endsWith(
+ "-4A0CCD2DDC7995083D73F5D667100C8A5831F16D")) {
+ allStatuses.add(file);
+ }
+ }
+ }
+ System.out.println("Scanning " + allStatuses.size() + " bridge "
+ + "network statuses.");
+
+ /* Parse statuses in forward order to detect stable fingerprint/
+ * address combinations to correct some of the IP address changes from
+ * one month to the next. */
+ SortedSet<String> stableFingerprintsAndAddresses =
+ new TreeSet<String>();
+ File stableFingerprintsAndAddressesFile =
+ new File("stable-fingerprints-and-addresses");
+ if (stableFingerprintsAndAddressesFile.exists()) {
+ System.out.println("Reading stable fingerprints and addresses from "
+ + "disk...");
+ BufferedReader br = new BufferedReader(new FileReader(
+ stableFingerprintsAndAddressesFile));
+ String line;
+ while ((line = br.readLine()) != null) {
+ stableFingerprintsAndAddresses.add(line);
+ }
+ br.close();
+ } else {
+ System.out.println("Parsing bridge network statuses and writing "
+ + "stable fingerprints and addresses to disk...");
+ Map<String, Long> firstSeenFingerprintAndAddress =
+ new HashMap<String, Long>();
+ for (File status : allStatuses) {
+ Set<String> fingerprints = new HashSet<String>();
+ BufferedReader br = new BufferedReader(new FileReader(status));
+ String line, rLine = null;
+ while ((line = br.readLine()) != null) {
+ if (line.startsWith("r ")) {
+ String[] parts = line.split(" ");
+ if (parts.length < 8) {
+ System.out.println("Illegal line '" + rLine + "' in "
+ + status + ". Skipping status.");
+ break;
+ }
+ String fingerprint = parts[2];
+ String address = parts[6];
+ String fingerprintAndAddress = fingerprint + " " + address;
+ if (stableFingerprintsAndAddresses.contains(
+ fingerprintAndAddress)) {
+ continue;
+ } else {
+ String date = parts[4];
+ String time = parts[5];
+ long published = isoFormat.parse(date + " " + time).
+ getTime();
+ if (!firstSeenFingerprintAndAddress.containsKey(
+ fingerprintAndAddress)) {
+ firstSeenFingerprintAndAddress.put(fingerprintAndAddress,
+ published);
+ } else if (published - firstSeenFingerprintAndAddress.get(
+ fingerprintAndAddress) > 36L * 60L * 60L * 1000L) {
+ stableFingerprintsAndAddresses.add(
+ fingerprintAndAddress);
+ }
+ }
+ }
+ }
+ br.close();
+ }
+ BufferedWriter bw = new BufferedWriter(new FileWriter(
+ stableFingerprintsAndAddressesFile));
+ for (String stableFingerprintAndAddress :
+ stableFingerprintsAndAddresses) {
+ bw.write(stableFingerprintAndAddress + "\n");
+ }
+ bw.close();
+ }
+ System.out.println("We know about "
+ + stableFingerprintsAndAddresses.size() + " stable fingerprints "
+ + "and addresses.");
+
+ /* Now parse statuses in reverse direction to calculate time until
+ * next address change and weighted fractional uptime for all bridges.
+ * Whenever we find a bridge published in a month for the first time,
+ * we look if we identified the bridge fingerprint and address (either
+ * new or old) as stable before. If we did, ignore this address
+ * change. */
+ File futureStabilityDirectory = new File("future-stability");
+ if (futureStabilityDirectory.exists()) {
+ System.out.println("Not overwriting files in "
+ + futureStabilityDirectory.getAbsolutePath());
+ } else {
+
+ /* Track weighted uptime and total weighted time in a long[2]. */
+ SortedMap<String, long[]> wfuHistory =
+ new TreeMap<String, long[]>();
+
+ /* Track timestamps of next address changes in a long. */
+ SortedMap<String, Long> nacHistory = new TreeMap<String, Long>();
+
+ /* Store the last known r lines by fingerprint to be able to decide
+ * whether a bridge has changed its IP address. */
+ Map<String, String> lastKnownRLines = new HashMap<String, String>();
+
+ /* Parse bridge network statuses in reverse order. */
+ SortedSet<File> allStatusesReverseOrder =
+ new TreeSet<File>(Collections.reverseOrder());
+ allStatusesReverseOrder.addAll(allStatuses);
+ long nextWeightingInterval = -1L, lastStatusPublicationMillis = -1L;
+ for (File status : allStatusesReverseOrder) {
+
+ /* Parse status publication time from file name. */
+ long statusPublicationMillis = statusFileNameFormat.parse(
+ status.getName().substring(0, "yyyyMMdd-HHmmss".length())).
+ getTime();
+
+ /* We're just looking at the first status outside the analysis
+ * interval. Stop parsing here. */
+ if (statusPublicationMillis < analyzeFromMillis) {
+ break;
+ }
+
+ /* Calculate the seconds since the last parsed status. If this is
+ * the first status or we haven't seen a status for more than 60
+ * minutes, assume 60 minutes. */
+ long secondsSinceLastStatusPublication =
+ lastStatusPublicationMillis < 0L ||
+ lastStatusPublicationMillis - statusPublicationMillis
+ > 60L * 60L * 1000L ? 60L * 60L
+ : (lastStatusPublicationMillis - statusPublicationMillis)
+ / 1000L;
+ lastStatusPublicationMillis = statusPublicationMillis;
+
+ /* Before parsing the next bridge network status, see if 12 hours
+ * have passed since we last discounted wfu history values. If
+ * so, discount variables for all known bridges by factor 0.95 (or
+ * 19/20 since these are long integers) and remove those bridges
+ * with a weighted fractional uptime below 1/10000 from the
+ * history. */
+ long weightingInterval = statusPublicationMillis
+ / (12L * 60L * 60L * 1000L);
+ if (nextWeightingInterval < 0L) {
+ nextWeightingInterval = weightingInterval;
+ }
+ while (weightingInterval < nextWeightingInterval) {
+ Set<String> bridgesToRemove = new HashSet<String>();
+ for (Map.Entry<String, long[]> e : wfuHistory.entrySet()) {
+ long[] w = e.getValue();
+ w[0] *= 19L;
+ w[0] /= 20L;
+ w[1] *= 19L;
+ w[1] /= 20L;
+ if (((10000L * w[0]) / w[1]) < 1L) {
+ bridgesToRemove.add(e.getKey());
+ }
+ }
+ for (String fingerprint : bridgesToRemove) {
+ wfuHistory.remove(fingerprint);
+ }
+ nextWeightingInterval -= 1L;
+ }
+
+ /* Increment total weighted time for all bridges by seconds
+ * since the last status was published. */
+ for (long[] w : wfuHistory.values()) {
+ w[1] += secondsSinceLastStatusPublication;
+ }
+
+ /* If the status falls within our analysis interval, write future
+ * WFUs and TOSAs for all running bridges to disk. */
+ BufferedWriter bw = null;
+ if (statusPublicationMillis <= analyzeToMillis) {
+ File futureStabilityFile = new File("future-stability",
+ futureStabilityFileNameFormat.format(
+ statusPublicationMillis));
+ futureStabilityFile.getParentFile().mkdirs();
+ bw = new BufferedWriter(new FileWriter(futureStabilityFile));
+ }
+
+
+ /* Parse r lines of all bridges with the Running flag from the
+ * current bridge network status. */
+ BufferedReader br = new BufferedReader(new FileReader(status));
+ String line, rLine = null;
+ SortedMap<String, String> runningBridges =
+ new TreeMap<String, String>();
+ while ((line = br.readLine()) != null) {
+ if (line.startsWith("r ")) {
+ rLine = line;
+ } else if (line.startsWith("s ") && line.contains(" Running")) {
+ String[] parts = rLine.split(" ");
+ if (parts.length < 8) {
+ System.out.println("Illegal line '" + rLine + "' in "
+ + status + ". Skipping line.");
+ continue;
+ }
+ String fingerprint = parts[2];
+ runningBridges.put(fingerprint, rLine);
+ }
+ }
+ br.close();
+
+ /* If this status doesn't contain a single bridge with the Running
+ * flag, ignore it. This is a problem with the bridge authority
+ * and doesn't mean we should consider all bridges as down. */
+ if (runningBridges.isEmpty()) {
+ continue;
+ }
+
+ /* Find out if a bridge changed its IP address or port. */
+ for (Map.Entry<String, String> e : runningBridges.entrySet()) {
+ String fingerprint = e.getKey();
+ String brLine = e.getValue();
+ String[] brParts = brLine.split(" ");
+
+ /* Increment weighted uptime by seconds since last status
+ * publication time. */
+ if (!wfuHistory.containsKey(fingerprint)) {
+ wfuHistory.put(fingerprint, new long[] {
+ secondsSinceLastStatusPublication,
+ secondsSinceLastStatusPublication });
+ } else {
+ wfuHistory.get(fingerprint)[0] +=
+ secondsSinceLastStatusPublication;
+ }
+
+ /* Check for address or port change. */
+ String address = brParts[6];
+ String port = brParts[7];
+ boolean sameAddressAndPort = false;
+ if (lastKnownRLines.containsKey(fingerprint)) {
+ String[] lastKnownRLineParts =
+ lastKnownRLines.get(fingerprint).split(" ");
+ String lastAddress = lastKnownRLineParts[6];
+ String lastPort = lastKnownRLineParts[7];
+ if (!port.equals(lastPort)) {
+ /* The port changed. It doesn't matter whether the
+ * address changed or not. */
+ } else if (address.equals(lastAddress)) {
+ /* The bridge's address and port are still the same. */
+ sameAddressAndPort = true;
+ } else {
+ String month = brParts[4].substring(0, "yyyy-MM".length());
+ String lastMonth = lastKnownRLineParts[4].substring(0,
+ "yyyy-MM".length());
+ if (!lastMonth.equals(month) &&
+ stableFingerprintsAndAddresses.contains(
+ fingerprint + " " + address) &&
+ stableFingerprintsAndAddresses.contains(
+ fingerprint + " " + lastAddress)) {
+ /* The last time we saw this bridge was in a different
+ * month. This bridge was seen with both addresses in
+ * intervals of 36 hours or more. Consider this
+ * address change an artifact from the sanitizing
+ * process. */
+ sameAddressAndPort = true;
+ } else {
+ /* Either the month did not change or the address or
+ * port did change. */
+ }
+ }
+ } else {
+ /* We're seeing this bridge for the first time. */
+ }
+ if (!sameAddressAndPort) {
+ nacHistory.put(fingerprint, statusPublicationMillis);
+ }
+ lastKnownRLines.put(fingerprint, brLine);
+
+ /* Write WFU and TOSA to disk. */
+ if (bw != null) {
+ long[] wfu = wfuHistory.get(fingerprint);
+ long tosa = (nacHistory.get(fingerprint)
+ - statusPublicationMillis) / 1000L;
+ bw.write(fingerprint + " " + tosa + " "
+ + ((10000L * wfu[0]) / wfu[1]) + " " + "\n");
+ }
+ }
+ br.close();
+ if (bw != null) {
+ bw.close();
+ }
+ }
+ }
+
+ /* Finally, parse statuses in forward order to calculate weighted mean
+ * time between address change (WMTBAC) and weighted fractional uptime
+ * (WFU) and simulate how stable bridges meeting given requirements
+ * are. */
+ File stabilityFile = new File("stability.csv");
+ if (stabilityFile.exists()) {
+ System.out.println("Not overwriting output file "
+ + stabilityFile.getAbsolutePath());
+ } else {
+
+ /* Run the simulation for the following WFU and WMTBAC
+ * percentiles: */
+ List<Integer> wfuPercentiles = new ArrayList<Integer>();
+ for (int l : new int[] { 0, 30, 40, 50, 60, 70 }) {
+ wfuPercentiles.add(l);
+ }
+ List<Integer> wmtbacPercentiles = new ArrayList<Integer>();
+ for (int l : new int[] { 0, 30, 40, 50, 60, 70 }) {
+ wmtbacPercentiles.add(l);
+ }
+
+ /* Add column headers to output file. */
+ SortedSet<String> columns = new TreeSet<String>();
+ columns.add("running");
+ columns.add("minwta");
+ columns.add("minwtb");
+ for (int wfuPercentile : wfuPercentiles) {
+ columns.add("minwfua" + wfuPercentile + "wfu");
+ columns.add("minwfub" + wfuPercentile + "wfu");
+ for (int wmtbacPercentile : wmtbacPercentiles) {
+ String simulation = wfuPercentile + "wfu" + wmtbacPercentile
+ + "wmtbac";
+ columns.add("stablebridge" + simulation);
+ columns.add("perc15wfu" + simulation);
+ columns.add("perc10wfu" + simulation);
+ columns.add("perc5wfu" + simulation);
+ columns.add("perc15tosa" + simulation);
+ columns.add("perc10tosa" + simulation);
+ columns.add("perc5tosa" + simulation);
+ }
+ }
+ for (int wmtbacPercentile : wmtbacPercentiles) {
+ columns.add("minwmtbaca" + wmtbacPercentile + "wmtbac");
+ columns.add("minwmtbacb" + wmtbacPercentile + "wmtbac");
+ }
+ BufferedWriter bw = new BufferedWriter(new FileWriter(
+ stabilityFile));
+ bw.write("time");
+ for (String column : columns) {
+ bw.write("," + column);
+ }
+ bw.write("\n");
+
+ SortedMap<String, BridgeHistoryElement> history =
+ new TreeMap<String, BridgeHistoryElement>();
+
+ /* Parse previously exported network status entries again, but this
+ * time in forward order. */
+ long nextWeightingInterval = -1L, lastStatusPublicationMillis = -1L;
+ for (File status : allStatuses) {
+
+ /* Parse status publication time from file name. */
+ long statusPublicationMillis = statusFileNameFormat.parse(
+ status.getName().substring(0, "yyyyMMdd-HHmmss".length())).
+ getTime();
+
+ /* Calculate the seconds since the last parsed status. If this is
+ * the first status or we haven't seen a status for more than 60
+ * minutes, assume 60 minutes. */
+ long secondsSinceLastStatusPublication =
+ lastStatusPublicationMillis < 0L ||
+ statusPublicationMillis - lastStatusPublicationMillis
+ > 60L * 60L * 1000L ? 60L * 60L
+ : (statusPublicationMillis - lastStatusPublicationMillis)
+ / 1000L;
+
+ /* Before parsing the next bridge network status, see if 12 hours
+ * have passed since we last discounted wfu and wmtbac history
+ * values. If so, discount variables for all known bridges by
+ * factor 0.95 (or 19/20 since these are long integers) and remove
+ * those bridges with a weighted fractional uptime below 1/10000
+ * from the history. Also, discount weighted run length and total
+ * run weights for all known relays by factor 0.95. */
+ long weightingInterval = statusPublicationMillis
+ / (12L * 60L * 60L * 1000L);
+ if (nextWeightingInterval < 0L) {
+ nextWeightingInterval = weightingInterval;
+ }
+ while (weightingInterval > nextWeightingInterval) {
+ Set<String> bridgesToRemove = new HashSet<String>();
+ for (Map.Entry<String, BridgeHistoryElement> e :
+ history.entrySet()) {
+ BridgeHistoryElement historyElement = e.getValue();
+ historyElement.weightedUptime =
+ (historyElement.weightedUptime * 19L) / 20L;
+ historyElement.weightedTime =
+ (historyElement.weightedTime * 19L) / 20L;
+ if (((10000L * historyElement.weightedUptime)
+ / historyElement.weightedTime) < 1L) {
+ String fingerprint = e.getKey();
+ bridgesToRemove.add(fingerprint);
+ }
+ historyElement.weightedRunLength *= 0.95;
+ historyElement.totalRunWeights *= 0.95;
+ }
+ for (String fingerprint : bridgesToRemove) {
+ history.remove(fingerprint);
+ }
+ nextWeightingInterval += 1L;
+ }
+
+ /* Parse r lines of all bridges with the Running flag from the
+ * current bridge network status. */
+ BufferedReader br = new BufferedReader(new FileReader(status));
+ String line, rLine = null;
+ SortedMap<String, String> runningBridges =
+ new TreeMap<String, String>();
+ while ((line = br.readLine()) != null) {
+ if (line.startsWith("r ")) {
+ rLine = line;
+ } else if (line.startsWith("s ") && line.contains(" Running")) {
+ String[] parts = rLine.split(" ");
+ if (parts.length < 8) {
+ System.out.println("Illegal line '" + rLine + "' in "
+ + status + ". Skipping line.");
+ continue;
+ }
+ String fingerprint = parts[2];
+ runningBridges.put(fingerprint, rLine);
+ }
+ }
+ br.close();
+
+ /* If this status doesn't contain a single bridge with the Running
+ * flag, ignore it. This is a problem with the bridge authority
+ * and doesn't mean we should consider all bridges as down. */
+ if (runningBridges.isEmpty()) {
+ continue;
+ }
+
+ /* Add new bridges to history or update history if it already
+ * contains a bridge. */
+ for (Map.Entry<String, String> e : runningBridges.entrySet()) {
+ String fingerprint = e.getKey();
+ String brLine = e.getValue();
+ String[] brParts = brLine.split(" ");
+ String address = brParts[6];
+ int port = Integer.parseInt(brParts[7]);
+ String month = brParts[4].substring(0, "yyyy-MM".length());
+ BridgeHistoryElement bhe = null;
+ if (!history.containsKey(fingerprint)) {
+
+ /* Add new bridge to history. */
+ bhe = new BridgeHistoryElement();
+ bhe.lastSeenWithDifferentAddressAndPort =
+ lastStatusPublicationMillis;
+ history.put(fingerprint, bhe);
+ } else {
+
+ /* Update bridge in history. */
+ bhe = history.get(fingerprint);
+
+ /* If the port changed, ... */
+ if (port != bhe.port ||
+
+ /* ... or the address changed and ... */
+ (!address.equals(bhe.address) &&
+
+ /* ... either the month is the same ... */
+ (month.equals(bhe.month) ||
+
+ /* ... or this address is not stable ... */
+ !stableFingerprintsAndAddresses.contains(
+ fingerprint + " " + address) ||
+
+ /* ... or the last address is not stable, ... */
+ !stableFingerprintsAndAddresses.contains(
+ fingerprint + " " + bhe.address)))) {
+
+ /* ... assume that the bridge changed its address or
+ * port. */
+ bhe.weightedRunLength += (double)
+ ((bhe.lastSeenWithThisAddressAndPort -
+ bhe.lastSeenWithDifferentAddressAndPort) / 1000L);
+ bhe.totalRunWeights += 1.0;
+ bhe.lastSeenWithDifferentAddressAndPort =
+ bhe.lastSeenWithThisAddressAndPort;
+ }
+ }
+
+ /* Regardless of whether the bridge is new, kept or changed
+ * its address and port, raise its WFU times and note its
+ * current address, month, and port, and that we saw it using
+ * them. */
+ bhe.weightedUptime += secondsSinceLastStatusPublication;
+ bhe.weightedTime += secondsSinceLastStatusPublication;
+ bhe.lastSeenWithThisAddressAndPort = statusPublicationMillis;
+ bhe.address = address;
+ bhe.month = month;
+ bhe.port = port;
+ }
+
+ /* Update weighted times (not uptimes) of non-running bridges. */
+ for (Map.Entry<String, BridgeHistoryElement> e :
+ history.entrySet()) {
+ String fingerprint = e.getKey();
+ if (!runningBridges.containsKey(fingerprint)) {
+ BridgeHistoryElement bhe = e.getValue();
+ bhe.weightedTime += secondsSinceLastStatusPublication;
+ }
+ }
+
+ /* Prepare writing results. */
+ Map<String, String> results = new HashMap<String, String>();
+ results.put("running", "" + runningBridges.size());
+
+ /* If we reached the analysis interval, read previously calculated
+ * future WFUs and TOSAs from disk and run the simulations. */
+ Map<String, Long> fwfus = new HashMap<String, Long>(),
+ tosas = new HashMap<String, Long>();
+ File futureStabilityFile = new File("future-stability",
+ futureStabilityFileNameFormat.format(
+ statusPublicationMillis));
+ if (statusPublicationMillis < analyzeFromMillis ||
+ statusPublicationMillis > analyzeToMillis) {
+ /* Outside of our analysis interval. Skip simulation. */
+ } else if (!futureStabilityFile.exists()) {
+ System.out.println("Could not find file "
+ + futureStabilityFile + ". Skipping simulation!");
+ } else {
+ BufferedReader fsBr = new BufferedReader(new FileReader(
+ futureStabilityFile));
+ String fsLine;
+ while ((fsLine = fsBr.readLine()) != null) {
+ String[] fsParts = fsLine.split(" ");
+ tosas.put(fsParts[0], Long.parseLong(fsParts[1]));
+ fwfus.put(fsParts[0], Long.parseLong(fsParts[2]));
+ }
+ fsBr.close();
+
+ /* Prepare calculating thresholds for selecting stable bridges
+ * in simulations. */
+ List<Long> totalWeightedTimes = new ArrayList<Long>();
+ for (String fingerprint : runningBridges.keySet()) {
+ totalWeightedTimes.add(history.get(fingerprint).weightedTime);
+ }
+ Collections.sort(totalWeightedTimes);
+ long minimumTotalWeightedTime = totalWeightedTimes.get(
+ totalWeightedTimes.size() / 8);
+ results.put("minwta", String.valueOf(minimumTotalWeightedTime));
+ if (minimumTotalWeightedTime > 8L * 24L * 60L * 60L) {
+ minimumTotalWeightedTime = 8L * 24L * 60L * 60L;
+ }
+ results.put("minwtb", String.valueOf(minimumTotalWeightedTime));
+ List<Long> weightedFractionalUptimesFamiliar =
+ new ArrayList<Long>();
+ for (String fingerprint : runningBridges.keySet()) {
+ BridgeHistoryElement bhe = history.get(fingerprint);
+ if (bhe.weightedTime >= minimumTotalWeightedTime) {
+ long weightedFractionalUptime =
+ (10000L * bhe.weightedUptime) / bhe.weightedTime;
+ weightedFractionalUptimesFamiliar.add(
+ weightedFractionalUptime);
+ }
+ }
+ Collections.sort(weightedFractionalUptimesFamiliar);
+ List<Long> wmtbacs = new ArrayList<Long>();
+ for (String fingerprint : runningBridges.keySet()) {
+ BridgeHistoryElement bhe = history.get(fingerprint);
+ double totalRunLength = bhe.weightedRunLength + (double)
+ ((bhe.lastSeenWithThisAddressAndPort -
+ bhe.lastSeenWithDifferentAddressAndPort) / 1000L);
+ double totalWeights = bhe.totalRunWeights + 1.0;
+ long wmtbac = totalWeights < 0.0001 ? 0L
+ : (long) (totalRunLength / totalWeights);
+ wmtbacs.add(wmtbac);
+ }
+ Collections.sort(wmtbacs);
+
+ /* Run simulation for the bridges in the current status for
+ * various WFU and WMTBAC percentiles. */
+ for (int wmtbacPercentile : wmtbacPercentiles) {
+ for (int wfuPercentile : wfuPercentiles) {
+ String simulation = wfuPercentile + "wfu" + wmtbacPercentile
+ + "wmtbac";
+ long minimumWeightedMeanTimeBetweenAddressChange =
+ wmtbacs.get((wmtbacPercentile * wmtbacs.size()) / 100);
+ results.put("minwmtbaca" + wmtbacPercentile + "wmtbac",
+ String.valueOf(
+ minimumWeightedMeanTimeBetweenAddressChange));
+ if (minimumWeightedMeanTimeBetweenAddressChange >
+ 30L * 24L * 60L * 60L) {
+ minimumWeightedMeanTimeBetweenAddressChange =
+ 30L * 24L * 60L * 60L;
+ }
+ results.put("minwmtbacb" + wmtbacPercentile + "wmtbac",
+ String.valueOf(
+ minimumWeightedMeanTimeBetweenAddressChange));
+ long minimumWeightedFractionalUptime =
+ weightedFractionalUptimesFamiliar.get((wfuPercentile
+ * weightedFractionalUptimesFamiliar.size()) / 100);
+ results.put("minwfua" + wfuPercentile + "wfu",
+ String.valueOf(minimumWeightedFractionalUptime));
+ if (minimumWeightedFractionalUptime > 9800) {
+ minimumWeightedFractionalUptime = 9800;
+ }
+ results.put("minwfub" + wfuPercentile + "wfu",
+ String.valueOf(minimumWeightedFractionalUptime));
+ List<Long> fwfuList = new ArrayList<Long>(),
+ tosaList = new ArrayList<Long>();
+ Set<String> selectedBridges = new HashSet<String>();
+ for (String fingerprint : runningBridges.keySet()) {
+ BridgeHistoryElement bhe = history.get(fingerprint);
+ double totalRunLength = bhe.weightedRunLength + (double)
+ ((bhe.lastSeenWithThisAddressAndPort -
+ bhe.lastSeenWithDifferentAddressAndPort) / 1000L);
+ double totalWeights = bhe.totalRunWeights + 1.0;
+ long wmtbac = totalWeights < 0.0001 ? 0L
+ : (long) (totalRunLength / totalWeights);
+ if (wmtbac < minimumWeightedMeanTimeBetweenAddressChange) {
+ continue;
+ }
+ if (wfuPercentile > 0 &&
+ bhe.weightedTime < minimumTotalWeightedTime) {
+ continue;
+ }
+ long weightedFractionalUptime =
+ (10000L * bhe.weightedUptime) / bhe.weightedTime;
+ if (weightedFractionalUptime <
+ minimumWeightedFractionalUptime) {
+ continue;
+ }
+ long fwfu = fwfus.get(fingerprint);
+ fwfuList.add(fwfu);
+ long tosa = tosas.get(fingerprint);
+ tosaList.add(tosa);
+ selectedBridges.add(fingerprint);
+ }
+ /* Calculate percentiles of future WFU and of TOSA as the
+ * simulation results. */
+ results.put("stablebridge" + simulation,
+ String.valueOf(selectedBridges.size()));
+ if (tosaList.size() > 0L) {
+ Collections.sort(tosaList);
+ results.put("perc15tosa" + simulation,
+ "" + tosaList.get((15 * tosaList.size()) / 100));
+ results.put("perc10tosa" + simulation,
+ "" + tosaList.get((10 * tosaList.size()) / 100));
+ results.put("perc5tosa" + simulation,
+ "" + tosaList.get((5 * tosaList.size()) / 100));
+ }
+ if (fwfuList.size() > 0) {
+ Collections.sort(fwfuList);
+ results.put("perc15wfu" + simulation,
+ "" + fwfuList.get((15 * fwfuList.size()) / 100));
+ results.put("perc10wfu" + simulation,
+ "" + fwfuList.get((10 * fwfuList.size()) / 100));
+ results.put("perc5wfu" + simulation,
+ "" + fwfuList.get((5 * fwfuList.size()) / 100));
+ }
+ }
+ }
+ }
+
+ /* Write results, regardless of whether we ran simulations or
+ * not. */
+ SortedSet<String> missingColumns = new TreeSet<String>();
+ for (String resultColumn : results.keySet()) {
+ if (!columns.contains(resultColumn)) {
+ missingColumns.add(resultColumn);
+ }
+ }
+ if (!missingColumns.isEmpty()) {
+ System.out.println("We are missing the following columns:");
+ for (String missingColumn : missingColumns) {
+ System.out.print(" " + missingColumn);
+ }
+ System.out.println(". Ignoring.");
+ }
+ bw.write(isoFormat.format(statusPublicationMillis));
+ for (String column : columns) {
+ if (results.containsKey(column)) {
+ bw.write("," + results.get(column));
+ } else {
+ bw.write(",NA");
+ }
+ }
+ bw.write("\n");
+ lastStatusPublicationMillis = statusPublicationMillis;
+ }
+ bw.close();
+ }
+ }
+}
+
diff --git a/task-4255/report.bib b/task-4255/report.bib
new file mode 100644
index 0000000..427e27c
--- /dev/null
+++ b/task-4255/report.bib
@@ -0,0 +1,26 @@
+@misc{dirspec,
+ author = {Roger Dingledine and Nick Mathewson},
+ title = {Tor directory protocol, version 3},
+ note = {\url{https://gitweb.torproject.org/tor.git/blob_plain/HEAD:/doc/spec/dir-spec.txt}},
+}
+
+@techreport{loesing2011analysis,
+ title = {An Analysis of {Tor} Relay Stability},
+ author = {Karsten Loesing},
+ institution = {The Tor Project},
+ year = {2011},
+ month = {June},
+ type = {Technical Report},
+ note = {\url{https://metrics.torproject.org/papers/relay-stability-2011-06-30.pdf}},
+}
+
+@techreport{loesing2011overview,
+ title = {Overview of Statistical Data in the {Tor} Network},
+ author = {Karsten Loesing},
+ institution = {The Tor Project},
+ year = {2011},
+ month = {March},
+ type = {Technical Report},
+ note = {\url{https://metrics.torproject.org/papers/data-2011-03-14.pdf}},
+}
+
diff --git a/task-4255/report.tex b/task-4255/report.tex
new file mode 100644
index 0000000..0dfd437
--- /dev/null
+++ b/task-4255/report.tex
@@ -0,0 +1,414 @@
+\documentclass{article}
+\usepackage{url}
+\usepackage[pdftex]{graphicx}
+\usepackage{graphics}
+\usepackage{color}
+\begin{document}
+\title{An Analysis of Tor Bridge Stability\\
+{\large --- Making BridgeDB give out at least one stable bridge per
+user ---}}
+\author{Karsten Loesing\\{\tt karsten@xxxxxxxxxxxxxx}}
+
+\maketitle
+
+\section{Introducing the instable bridges problem}
+
+As of October 2011, the Tor network consists of a few hundred thousand
+clients, 2\,400 public relays, and about 600 non-public bridge relays.
+Bridge relays (in the following: bridges) are entry points which are not
+publicly listed to prevent censors from blocking access to the Tor
+network.
+Censored users request a small number of typically three bridge addresses
+from the BridgeDB service via email or http and then connect to the Tor
+network only via these bridges.
+If all bridges that a user knows about suddenly stop working, the user
+needs to request a new set of bridge addresses from BridgeDB.
+However, BridgeDB memorizes the user's email or IP address and only gives
+out new bridges every 24 hours to slow down enumeration attempts.
+The result is that a user who is unlucky enough to receive only unreliable
+bridges from BridgeDB won't be able to connect to the Tor network for up
+to 24 hours before requesting a new set of bridges.
+
+In this report we propose that BridgeDB keeps bridge stability records,
+similar to how the directory authorities keep relay stability records, and
+includes at least one stable bridge in its responses to users.
+In fact, BridgeDB currently attempts to do this by including at least one
+bridge with the Stable flag assigned by the bridge authority in its
+results.
+This approach is broken for two reasons:
+The first reason is that the algorithm that the bridge authority uses to
+assign the Stable flag is broken to the extent that almost every bridge
+has the Stable flag assigned.
+The second reason is that the Stable flag was designed for clients to
+select relays for long-running streams, not for BridgeDB to select
+reliable entry points into the Tor network.
+A better metric for stable bridges would be based on bridge uptime and on
+the frequency of IP address changes.
+We propose such a metric and evaluate its effectiveness for selecting
+stable bridges based on archived bridge directories.
+
+\section{Defining a new bridge stability metric}
+\label{sec:defining}
+
+The directory authorities implement a few relay stability metrics to
+decide which of the relays to assign the Guard and Stable
+flag~\cite{dirspec, loesing2011analysis}.
+The requirements for stable bridges that we propose here are similar to
+the entry guard requirements.
+That is, stable bridges should have a higher fractional uptime than
+non-stable ones.
+Further, a stable bridge should be available under the same IP address and
+TCP port.
+Otherwise, bridge users who only know a bridge address won't be able to
+connect to the bridge once it changes its address or port.
+We propose the following requirements for a bridge to be considered
+stable in the style of the Guard and Stable flag definition:
+
+\label{def:bridgestability}
+\begin{quote}
+A bridge is considered stable if its \emph{Weighted Mean Time Between
+Address Change} is at least the median for known active bridges or at
+least 30~days, if it is `familiar', and if its \emph{Weighted Fractional
+Uptime} is at least the median for `familiar' active bridges or at least
+98~\%.
+A bridge is `familiar' if 1/8 of all active bridges have appeared more
+recently than it, or if it has been around for a \emph{Weighted Time} of
+8~days.
+\end{quote}
+
+This bridge stability definition contains three main requirements:
+
+\begin{itemize}
+\item The \emph{Weighted Mean Time Between Address Change (WMTBAC)}
+metric is used to track the time that a bridge typically uses the same IP
+address and TCP port.
+The (unweighted) MTBAC measures the average time between last using
+address and port $a_0$ to last using address and port $a_1$.
+This metric is weighted to put more emphasis on recent events than on past
+events.
+Therefore, past address sessions are discounted by factor $0.95$ every
+12~hours.
+The current session is not discounted, so that a WMTBAC value of 30 days
+can be reached after 30 days at the earliest.
+\item The \emph{Weighted Fractional Uptime (WFU)} metric measures the
+fraction of bridge uptime in the past.
+Similar to WMTBAC, WFU values are discounted by factor $0.95$ every
+12~hours, but in this case including the current uptime session.
+\item The \emph{Weighted Time (WT)} metric is used to calculate a bridge's
+WFU and to decide whether a bridge is around long enough to be considered
+`familiar.'
+WT is discounted similar to WMTBAC and WFU, so that a WT of 8 days can be
+reached after around 16 days at the earliest.
+\end{itemize}
+
+All three requirements consist of a dynamic part that depends on the
+stability of other bridges (e.g., ``A bridge is familiar if 1/8 of all
+active bridges have appeared more recently than it, \ldots'') and a static
+part that is independent of other bridges (e.g., ``\ldots or if it has
+been around for a Weighted Time of 8 days.'').
+The dynamic parts ensure that a certain fraction of bridges is considered
+stable even in a rather instable network.
+The static parts ensures that rather stable bridges are not excluded even
+when most other bridges in the network are stable.
+
+\section{Extending BridgeDB to track bridge stability}
+
+There are at least two code bases that could be extended to track bridge
+stability and include at least one stable bridge in BridgeDB results: the
+bridge authority and BridgeDB.
+The decision for extending either code base affects the available data
+for tracking bridge stability and is therefore discussed here.
+
+The bridge authority maintains a list of all active bridges.
+Bridges register at the bridge authority when joining the network, and the
+bridge authority periodically performs reachability tests to confirm that
+a bridge is still active.
+The bridge authority takes snapshots of the list of active bridges every
+30~minutes and copies these snapshots to BridgeDB.
+BridgeDB parses these half-hourly snapshots and gives out bridges to users
+based on the most recently known snapshot.
+
+The bridge stability history can be implemented either in the bridge
+authority code or in BridgeDB.
+On the one hand, an implementation in BridgeDB has the disadvantage that
+bridge reachability data has a resolution of 30 minutes whereas the bridge
+authority would learn about bridges joining or leaving the network
+immediately.
+On the other hand, the bridge stability information is not used by
+anything in the Tor software, but only by BridgeDB.
+Implementing this feature in BridgeDB makes more sense from a software
+architecture point of view.
+In the following we assume that BridgeDB will track bridge stability based
+on half-hourly snapshots of active bridge lists, the bridge network
+statuses.
+
+\section{Simulating bridge stability using archived data}
+
+We can analyze how BridgeDB would track bridge stability and give out
+stable bridges by using archived bridge descriptors.
+These archives contain the same descriptors that BridgeDB uses, but they
+are public and don't contain any IP addresses or sensitive pieces of
+information.
+In Section~\ref{sec:missingdata} we look at the problem of missing data
+due to either the bridge authority or BridgeDB failing and at the effect
+on tracking bridge stability.
+We then touch the topic of how bridge descriptors are sanitized and how we
+can glue them back together for our analysis in
+Section~\ref{sec:sanitizing}.
+Next, we examine typical bridge stability values as requirements for
+considering a bridge as stable in Section~\ref{sec:requirements}.
+In Section~\ref{sec:fractions} we estimate what fraction of bridges would
+be considered as stable depending on the chosen stability requirements.
+Finally, in Section~\ref{sec:selectedstability} we evaluate how effective
+different requirement combinations are for selecting stable bridges.
+Result metrics are how soon selected bridges change their address or what
+fractional uptime selected bridges have in the future.
+
+\subsection{Handling missing bridge status data}
+\label{sec:missingdata}
+
+The bridge status data that we use in this analysis and that would also be
+used by BridgeDB to track bridge stability is generated by the bridge
+authority and copied over to BridgeDB every 30~minutes.
+Figure~\ref{fig:runningbridge} shows the number of running bridges
+contained in these snapshots from July 2010 to June 2011.
+
+\begin{figure}[t]
+\includegraphics[width=\textwidth]{runningbridge.pdf}
+\caption{Median number of running bridges as reported by the bridge
+authority}
+\label{fig:runningbridge}
+\end{figure}
+
+For most of the time the number of bridges is relatively stable.
+But there are at least two irregularities, one in July 2010 and another
+one in February 2011, resulting from problems with the bridge authority or
+the data transfer to the BridgeDB host.
+Figure~\ref{fig:runningbridge-detail} shows these two intervals in more
+detail.
+
+\begin{figure}[t]
+\includegraphics[width=\textwidth]{runningbridge-detail.pdf}
+\caption{Number of Running bridges during phases when either the bridge
+authority or the BridgeDB host were broken}
+\label{fig:runningbridge-detail}
+\end{figure}
+
+The missing data from July 14 to 27, 2010 comes from BridgeDB host not
+accepting new descriptors from the bridge authority because of an
+operating system upgrade of the BridgeDB host.
+During this time, the bridge authority continued to work, but BridgeDB was
+unable to learn about new bridge descriptors from it.
+
+During the time from January 31 to February 16, 2011, the \verb+tor+
+process running the bridge authority silently died, but the script to copy
+descriptors to BridgeDB kept running.
+In this case, BridgeDB received fresh tarballs containing stale
+descriptors with a constant number of 687 relays, visualized in light
+gray.
+These stale descriptors have been excluded from the sanitized descriptors
+and the subsequent analysis.
+The bridge authority was restarted on February 16, 2011, resulting in the
+number of running bridges slowly stabilizing throughout the day.
+
+Both this analysis and a later implementation in BridgeDB need to take
+extended phases of missing or stale data into account.
+
+\subsection{Detecting address changes in sanitized descriptors}
+\label{sec:sanitizing}
+
+The bridge descriptor archives that we use in this analysis have been
+sanitized to remove all addresses and otherwise sensitive
+parts~\cite{loesing2011overview}.
+Part of this sanitizing process is that bridge IP addresses are replaced
+with keyed hashes using a fresh key every month.
+More precisely, every bridge IP address is replaced with the private IP
+address \verb+10.x.x.x+ with \verb+x.x.x+ being the 3 most significant
+bytes of \verb+SHA-256(IP address | bridge identity | secret)+.
+
+A side-effect of this sanitizing step is that a bridge's sanitized IP
+address changes at least once per month, even if the bridge's real IP
+address stays the same.
+We need to detect these artificial address changes and distinguish them
+from real IP address changes.
+
+In this analysis we use a simple heuristic to distinguish between real IP
+address changes and artifacts from the sanitizing process:
+Whenever we find that a bridge has changed its IP address from one month
+to the next, we look up how long both IP addresses were in use in either
+month.
+If both addresses were contained in bridge descriptors that were published
+at least 36~hours apart, we consider them stable IP addresses and
+attribute the apparent IP address change to the sanitizing process.
+Otherwise, we assume the bridge has really changed its IP address.
+Obviously, this simple heuristic might lead us to false conclusions in
+some cases.
+But it helps us handle cases when bridges rarely or never change their IP
+address which would otherwise suffer from monthly address changes in this
+analysis.
+
+\subsection{Examining typical stability metric values}
+\label{sec:requirements}
+
+The definition of bridge stability on page~\pageref{sec:defining} contains
+three different metrics, each of which having a dynamic and a static part.
+The dynamic parts compares the value of a bridge's stability metric to the
+whole set of running bridges.
+Only those bridges are considered as stable that exceed the median value
+(or the 12.5th percentile) of all running bridges.
+The static requirement parts are fixed values for all stability metrics
+that don't rely on the stability of other bridges.
+
+Figure~\ref{fig:requirements} visualizes the dynamic (solid lines) and
+static parts (dashed lines) of all three requirements.
+The dynamic WMTBAC requirements are higher than previously expected.
+A value of 60 means that, on average, bridges keep their IP address and
+port for 60 days.
+The dynamic values are cut off at 30 days by the static requirement which
+should be a high enough value.
+The goal here is to give blocked users a stable enough set of bridges so
+that they don't have to wait another 24~hours before receiving new ones.
+
+We can further see that the dynamic requirements are relatively stable
+over time except for the two phases of missing bridge status data.
+The first phase in July 2010 mostly affects WT, but neither WMTBAC nor
+WFU.
+The second phase in February 2011 affects all three metrics.
+We can expect the selection of stable bridges during February 2010 to be
+more random than at other times.
+
+\begin{figure}[t]
+\includegraphics[width=\textwidth]{requirements.pdf}
+\caption{Dynamic requirements for considering a bridge as stable}
+\label{fig:requirements}
+\end{figure}
+
+\subsection{Estimating fractions of bridges considered as stable}
+\label{sec:fractions}
+
+Requiring a bridge to meet or exceed either or both WMTBF or WFU metric
+results in considering only a subset of all bridges as stable.
+The first result of this analysis is to outline what fraction of bridges
+would be considered as stable if BridgeDB used either or both
+requirements.
+In theory, all parameters in the bridge stability definition on
+page~\pageref{def:bridgestability} could be adjusted to change the set of
+stable bridges or focus more on address changes or on fractional uptime.
+We're leaving the fine-tuning for future work when specifying and
+implementing the BridgeDB extension.
+
+Figure~\ref{fig:stablebridge} shows the fraction of stable bridges over
+time.
+If we only require bridges to meet or exceed the median WMTBAC or the
+fixed value of 30 days, roughly 55~\% of the bridges are considered as
+stable.
+If bridges are only required to meet or exceed the WT and WFU values,
+about $7/8 \times 1/2 = 43.75~\%$ of bridges are considered as stable.
+Requiring both WFU and WMTBAC leads to a fraction of roughly 35~\% stable
+bridges.
+
+\begin{figure}[t]
+\includegraphics[width=\textwidth]{stablebridge.pdf}
+\caption{Impact of requiring stable bridges to meet or exceed the median
+WFU and/or WMTBAC on the fraction of running bridges considered as stable}
+\label{fig:stablebridge}
+\end{figure}
+
+The fraction of 33~\% stable bridges seems appropriate if 1 out of
+3~bridges in the BridgeDB results is supposed to be a stable bridge.
+If more than 1~bridge should be a stable bridge, the requirements need to
+be lowered, so that a higher fraction of bridges is considered stable.
+Otherwise, the load on stable bridges might become too high.
+
+\subsection{Evaluating different requirements on stable bridges}
+\label{sec:selectedstability}
+
+The main purpose of this analysis is to compare the quality of certain
+requirements and requirement combinations on the stability of selected
+bridges.
+Similar to the previous section, we only compare whether or not the WMTBAC
+or WFU requirement is used, but don't change their parameters.
+
+The first result is the future uptime that we can expect from a bridge
+that we consider stable.
+We calculate future uptime similar to past uptime by weighting events in
+the near future more than those happening later.
+We are particularly interested in the almost worst-case scenario here,
+which is why we're looking at the 10th percentile weighted fractional
+uptime in the future.
+This number means that 10~\% of bridges have a weighted fractional uptime
+at most this high and 90~\% of bridges have a value at least this high.
+
+Figure~\ref{fig:fwfu-sim} visualizes the four possible combinations of
+using or not using the WMTBAC and WFU requirements.
+In this plot, the ``WFU \& WMTBAC'' and ``WFU'' lines almost entirely
+overlap, meaning that the WMTBAC requirement doesn't add anything to
+future uptime of selected bridges.
+If the WFU requirement is not used, requiring bridges to meet the WMTBAC
+requirement increases future uptime from roughly 35~\% to maybe 55~\%.
+That means that there is a slight correlation between the two metrics,
+which is plausible.
+
+\begin{figure}[t]
+\includegraphics[width=\textwidth]{fwfu-sim.pdf}
+\caption{Impact of requiring stable bridges to meet or exceed the median
+WFU and/or WMTBAC on the 10th percentile weighted fractional uptime in the
+future}
+\label{fig:fwfu-sim}
+\end{figure}
+
+The second result is the time that a selected bridge stays on the same
+address and port.
+We simply measure the time that the bridge will keep using its current
+address in days.
+Again, we look at the 10th percentile.
+90~\% of selected bridges keep their address longer than this time.
+
+Figure~\ref{fig:tosa-sim} shows for how long bridges keep their address
+and port.
+Bridges meeting both WFU and WTMBAC requirements keep their address for 2
+to 5~weeks.
+This value decreases to 1 to 3~weeks when taking away the WFU requirement,
+which is also a result of the two metrics beeing correlated.
+The bridges that only meet the WFU requirement and not the WMTBAC
+requirement change their address within the first week.
+If we don't use any requirement at all, which is what BridgeDB does today,
+10~\% of all bridges change their address within a single day.
+
+\begin{figure}[t]
+\includegraphics[width=\textwidth]{tosa-sim.pdf}
+\caption{Impact of requiring stable bridges to meet or exceed the median
+WFU and/or WMTBAC on the 10th percentile time on the same address}
+\label{fig:tosa-sim}
+\end{figure}
+
+\section{Concluding the bridge stability analysis}
+
+In this report we propose to extend BridgeDB to make it give out at least
+one stable bridge per user.
+Bridge stability can be calculated based on bridge status information over
+time, similar to how the directory authorities calculate relay stability.
+The bridge stability metric proposed here is based on a bridge's past
+uptime and the frequency of changing its address and/or port.
+Requiring at least 1 bridge of the 3 to be given out to users greatly
+reduces the worst case probability of all bridges being offline or
+changing their addresses or ports.
+The price for this increase in stability is that stable bridges will be
+given out more often than non-stable bridges and will therefore see more
+usage.
+
+We suggest to implement the described bridge stability metric in BridgeDB
+and make it configurable to tweak the requirement parameters if needed.
+Maybe it turns out to be more useful to lower the requirements for a
+bridge to become stable and give out two stable bridges per response.
+It's also possible that the requirement for a bridge to keep its address
+becomes less important in the future when bridge clients can request a
+bridge's current address from the bridge authority.
+All these scenarios can be analyzed before deploying them using archived
+data as done in this report.
+
+\bibliography{report}
+\bibliographystyle{plain}
+
+\end{document}
+
diff --git a/task-4255/stability.R b/task-4255/stability.R
new file mode 100644
index 0000000..b2cd487
--- /dev/null
+++ b/task-4255/stability.R
@@ -0,0 +1,220 @@
+library(ggplot2)
+stability <- read.csv("stability.csv", stringsAsFactors = FALSE)
+
+d <- stability[stability$time > '2010-07' & stability$time < '2011-07', ]
+d <- d[, c("time", "running")]
+d <- na.omit(d)
+d_mean <- aggregate(list(running = d$running),
+ by = list(date = as.Date(d$time)), quantile, probs = 0.5)
+d_max <- aggregate(list(running = d$running),
+ by = list(date = as.Date(d$time)), quantile, probs = 0.75)
+d_min <- aggregate(list(running = d$running),
+ by = list(date = as.Date(d$time)), quantile, probs = 0.25)
+d <- data.frame(x = d_mean$date, y = d_mean$running, ymin = d_min$running,
+ ymax = d_max$running)
+d <- rbind(d,
+ data.frame(x = as.Date(setdiff(seq(from = min(d$x, na.rm = TRUE),
+ to = max(d$x, na.rm = TRUE), by="1 day"), d$x), origin = "1970-01-01"),
+ y = NA, ymin = NA, ymax = NA))
+ggplot(d, aes(x = as.Date(x), y = y, ymin = ymin, ymax = ymax)) +
+geom_line() +
+scale_x_date(name = "", major = "3 months", minor = "1 month",
+ format = "%b %Y") +
+scale_y_continuous(name = "Running \nbridges ",
+ limits = c(0, max(d_mean$running, na.rm = TRUE))) +
+opts(axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+ hjust = 0.5),
+ axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+ hjust = 1))
+ggsave(filename = "runningbridge.pdf", width = 7, height = 3, dpi = 100)
+
+pdf("runningbridge-detail.pdf", width = 7, height = 4)
+grid.newpage()
+pushViewport(viewport(layout = grid.layout(2, 1)))
+d <- stability[stability$time > '2010-07-10' &
+ stability$time < '2010-07-31', ]
+a <- ggplot(d, aes(x = as.POSIXct(time), y = running)) +
+geom_point(size = 0.75) +
+scale_x_datetime(name = "", major = "1 week", minor = "1 day",
+ format = "%b %d, %Y") +
+scale_y_continuous(name = "Running \nbridges ",
+ limits = c(0, max(d$running, na.rm = TRUE))) +
+opts(axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+ hjust = 0.5),
+ axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+ hjust = 1),
+ legend.position = "none")
+d <- stability[stability$time > '2011-01-29' &
+ stability$time < '2011-02-19', ]
+e <- read.csv("stale-bridge-tarballs.csv", stringsAsFactors = FALSE,
+ col.names = c("time"))
+d <- rbind(
+ data.frame(time = d$time, running = d$running, colour = "black"),
+ data.frame(time = e$time, running = 687, colour = "grey"))
+b <- ggplot(d, aes(x = as.POSIXct(time), y = running, colour = colour)) +
+geom_point(size = 0.75) +
+scale_x_datetime(name = "", major = "1 week", minor = "1 day",
+ format = "%b %d, %Y") +
+scale_y_continuous(name = "Running \nbridges ",
+ limits = c(0, max(d$running, na.rm = TRUE))) +
+scale_colour_manual(values = c("black", "grey60")) +
+opts(axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+ hjust = 0.5),
+ axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+ hjust = 1),
+ legend.position = "none")
+print(a, vp = viewport(layout.pos.row = 1, layout.pos.col = 1))
+print(b, vp = viewport(layout.pos.row = 2, layout.pos.col = 1))
+dev.off()
+
+d <- stability[stability$time > '2010-07' & stability$time < '2011-07', ]
+d <- d[, c("time", "minwmtbaca50wmtbac", "minwta", "minwfua50wfu")]
+d <- na.omit(d)
+d_mean <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)),
+ quantile, probs = 0.5)
+d_max <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)),
+ quantile, probs = 0.75)
+d_min <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)),
+ quantile, probs = 0.25)
+d <- rbind(
+ data.frame(x = d_mean$date,
+ y = d_mean$minwmtbaca50wmtbac / (24 * 60 * 60),
+ ymin = d_min$minwmtbaca50wmtbac / (24 * 60 * 60),
+ ymax = d_max$minwmtbaca50wmtbac / (24 * 60 * 60),
+ var = "Median WMTBAC"),
+ data.frame(x = d_mean$date, y = d_mean$minwta / (24 * 60 * 60),
+ ymin = d_min$minwta / (24 * 60 * 60),
+ ymax = d_max$minwta / (24 * 60 * 60),
+ var = "12.5th perc. WT"),
+ data.frame(x = d_mean$date, y = d_mean$minwfua50wfu / 10000,
+ ymin = d_min$minwfua50wfu / 10000,
+ ymax = d_max$minwfua50wfu / 10000,
+ var = "Median WFU"))
+missing_dates <- as.Date(setdiff(seq(from = min(d$x, na.rm = TRUE),
+ to = max(d$x, na.rm = TRUE), by="1 day"), d$x), origin = "1970-01-01")
+d <- rbind(d,
+ data.frame(x = missing_dates, y = NA, ymin = NA, ymax = NA,
+ var = "Median WMTBAC"),
+ data.frame(x = missing_dates, y = NA, ymin = NA, ymax = NA,
+ var = "12.5th perc. WT"),
+ data.frame(x = missing_dates, y = NA, ymin = NA, ymax = NA,
+ var = "Median WFU"))
+e <- data.frame(
+ yintercept = c(30, 8, 0.98),
+ var = c("Median WMTBAC", "12.5th perc. WT", "Median WFU"))
+ggplot(d, aes(x = as.Date(x), y = y, ymin = ymin, ymax = ymax)) +
+geom_line() +#colour = "grey30") +
+#geom_ribbon(alpha = 0.3) +
+geom_hline(data = e, aes(yintercept = yintercept), colour = "gray40",
+ linetype = 2) +
+facet_grid(var ~ ., scales = "free_y") +
+scale_x_date(name = "", major = "3 months", minor = "1 month",
+ format = "%b %Y") +
+scale_y_continuous(name = "") +
+opts(axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+ hjust = 0.5),
+ axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+ hjust = 1))
+ggsave(filename = "requirements.pdf", width = 7, height = 5, dpi = 100)
+
+d <- stability[stability$time > '2010-07' & stability$time < '2011-07', ]
+d <- d[, c("time", "perc10wfu0wfu0wmtbac", "perc10wfu0wfu50wmtbac",
+ "perc10wfu50wfu0wmtbac", "perc10wfu50wfu50wmtbac")]
+d <- na.omit(d)
+d <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)),
+ quantile, probs = 0.5)
+d <- rbind(d,
+ data.frame(date = as.Date(setdiff(seq(from = min(d$date),
+ to = max(d$date), by="1 day"), d$date), origin = "1970-01-01"),
+ perc10wfu0wfu0wmtbac = NA, perc10wfu0wfu50wmtbac = NA,
+ perc10wfu50wfu0wmtbac = NA, perc10wfu50wfu50wmtbac = NA))
+d <- melt(d, id = "date")
+ggplot(d, aes(x = date, y = value / 10000, linetype = variable)) +
+geom_line() +
+scale_y_continuous(name = paste("10th perc. \nWFU in \n",
+ "the future ", sep = ""), formatter = "percent", limits = c(0, 1)) +
+scale_x_date(name = "", major = "3 months", minor = "1 month",
+ format = "%b %Y") +
+scale_linetype_manual(name = paste("Requirements for\nconsidering",
+ "a\nbridge as stable\n"), breaks = c("perc10wfu50wfu50wmtbac",
+ "perc10wfu50wfu0wmtbac", "perc10wfu0wfu50wmtbac",
+ "perc10wfu0wfu0wmtbac"), labels = c("WFU & WMTBAC", "WFU", "WMTBAC",
+ "None"), values = c(1, 3, 2, 4)) +
+opts(plot.title = theme_text(size = 14 * 0.8, face = "bold"),
+ axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+ hjust = 0.5),
+ axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+ hjust = 1))
+ggsave(filename = "fwfu-sim.pdf", width = 7, height = 3, dpi = 100)
+
+d <- stability[stability$time > '2010-07' & stability$time < '2011-07', ]
+d <- d[, c("time", "perc10tosa0wfu0wmtbac", "perc10tosa0wfu50wmtbac",
+ "perc10tosa50wfu0wmtbac", "perc10tosa50wfu50wmtbac")]
+d <- na.omit(d)
+d <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)),
+ quantile, probs = 0.5)
+d <- rbind(d,
+ data.frame(date = as.Date(setdiff(seq(from = min(d$date),
+ to = max(d$date), by="1 day"), d$date), origin = "1970-01-01"),
+ perc10tosa0wfu0wmtbac = NA, perc10tosa0wfu50wmtbac = NA,
+ perc10tosa50wfu0wmtbac = NA, perc10tosa50wfu50wmtbac = NA))
+d <- melt(d, id = "date")
+ggplot(d, aes(x = date, y = value / 86400, linetype = variable)) +
+geom_line() +
+scale_y_continuous(name = paste("10th perc. \ntime on \nthe same \n",
+ "address \nin days ", sep = ""),
+ breaks = seq(0, max(d$value / 86400, na.rm = TRUE), 7),
+ minor = seq(0, max(d$value / 86400, na.rm = TRUE), 1),
+ limits = c(0, max(d$value / 86400, na.rm = TRUE))) +
+scale_x_date(name = "", major = "3 months", minor = "1 month",
+ format = "%b %Y") +
+scale_linetype_manual(name = paste("Requirements for\nconsidering",
+ "a\nbridge as stable\n"), breaks = c("perc10tosa50wfu50wmtbac",
+ "perc10tosa0wfu50wmtbac", "perc10tosa50wfu0wmtbac",
+ "perc10tosa0wfu0wmtbac"), labels = c("WFU & WMTBAC", "WMTBAC", "WFU",
+ "None"), values = c(1, 3, 2, 4)) +
+opts(plot.title = theme_text(size = 14 * 0.8, face = "bold"),
+ axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+ hjust = 0.5),
+ axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+ hjust = 1))
+ggsave(filename = "tosa-sim.pdf", width = 7, height = 3, dpi = 100)
+
+d <- stability[stability$time > '2010-07' & stability$time < '2011-07', ]
+d <- d[, c("time", "stablebridge0wfu50wmtbac", "stablebridge50wfu0wmtbac",
+ "stablebridge50wfu50wmtbac", "running")]
+d <- na.omit(d)
+#d <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)),
+# quantile, probs = 0.5)
+d <- rbind(
+ data.frame(time = d$time, y = d$stablebridge0wfu50wmtbac / d$running,
+ variable = "WMTBAC"),
+ data.frame(time = d$time, y = d$stablebridge50wfu0wmtbac / d$running,
+ variable = "WFU"),
+ data.frame(time = d$time, y = d$stablebridge50wfu50wmtbac / d$running,
+ variable = "WFU & WMTBAC"))
+d <- aggregate(list(y = d$y), by = list(x = as.Date(d$time),
+ variable = d$variable), quantile, probs = 0.5)
+missing_dates <- as.Date(setdiff(seq(from = min(d$x, na.rm = TRUE),
+ to = max(d$x, na.rm = TRUE), by="1 day"), d$x), origin = "1970-01-01")
+d <- rbind(d,
+ data.frame(x = missing_dates, y = NA,
+ variable = "WMTBAC"),
+ data.frame(x = missing_dates, y = NA,
+ variable = "WFU"),
+ data.frame(x = missing_dates, y = NA,
+ variable = "WFU & WMTBAC"))
+ggplot(d, aes(x = x, y = y, linetype = variable)) +
+geom_line() +
+scale_y_continuous(name = "Fraction of \nRunning \nbridges ",
+ formatter = "percent", limits = c(0, max(d$y, na.rm = TRUE))) +
+scale_x_date(name = "", major = "3 months", minor = "1 month",
+ format = "%b %Y") +
+scale_linetype_manual(name = paste("\nRequirements for\nconsidering",
+ "a\nbridge as stable\n"), values = c(3, 2, 4)) +
+opts(axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+ hjust = 0.5),
+ axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+ hjust = 1))
+ggsave(filename = "stablebridge.pdf", width = 7, height = 3, dpi = 100)
+
diff --git a/task-4255/stale-bridge-tarballs.csv b/task-4255/stale-bridge-tarballs.csv
new file mode 100644
index 0000000..ff430a4
--- /dev/null
+++ b/task-4255/stale-bridge-tarballs.csv
@@ -0,0 +1,760 @@
+2011-01-31 06:07:03
+2011-01-31 06:37:03
+2011-01-31 07:07:03
+2011-01-31 07:37:02
+2011-01-31 08:07:02
+2011-01-31 08:37:03
+2011-01-31 09:07:03
+2011-01-31 09:37:04
+2011-01-31 10:07:03
+2011-01-31 10:37:03
+2011-01-31 11:07:03
+2011-01-31 11:37:03
+2011-01-31 12:07:02
+2011-01-31 12:37:03
+2011-01-31 13:07:03
+2011-01-31 13:37:03
+2011-01-31 14:07:02
+2011-01-31 14:37:03
+2011-01-31 15:07:02
+2011-01-31 15:37:03
+2011-01-31 16:07:02
+2011-01-31 16:37:03
+2011-01-31 17:07:03
+2011-01-31 17:37:02
+2011-01-31 18:07:04
+2011-01-31 18:37:03
+2011-01-31 19:07:03
+2011-01-31 19:37:03
+2011-01-31 20:07:03
+2011-01-31 20:37:02
+2011-01-31 21:07:03
+2011-01-31 21:37:02
+2011-01-31 22:07:02
+2011-01-31 22:37:03
+2011-01-31 23:07:02
+2011-01-31 23:37:03
+2011-02-01 00:07:02
+2011-02-01 00:37:02
+2011-02-01 01:07:02
+2011-02-01 01:37:03
+2011-02-01 02:07:02
+2011-02-01 02:37:02
+2011-02-01 03:07:02
+2011-02-01 03:37:03
+2011-02-01 04:07:02
+2011-02-01 04:37:02
+2011-02-01 05:07:03
+2011-02-01 05:37:03
+2011-02-01 06:07:03
+2011-02-01 06:37:03
+2011-02-01 07:07:03
+2011-02-01 07:37:03
+2011-02-01 08:07:02
+2011-02-01 08:37:03
+2011-02-01 09:07:02
+2011-02-01 09:37:03
+2011-02-01 10:07:02
+2011-02-01 10:37:03
+2011-02-01 11:07:02
+2011-02-01 11:37:02
+2011-02-01 12:07:03
+2011-02-01 12:37:03
+2011-02-01 13:07:03
+2011-02-01 13:37:03
+2011-02-01 14:07:03
+2011-02-01 14:37:03
+2011-02-01 15:07:03
+2011-02-01 15:37:02
+2011-02-01 16:07:02
+2011-02-01 16:37:03
+2011-02-01 17:07:03
+2011-02-01 17:37:03
+2011-02-01 18:07:02
+2011-02-01 18:37:03
+2011-02-01 19:07:03
+2011-02-01 19:37:03
+2011-02-01 20:07:03
+2011-02-01 20:37:02
+2011-02-01 21:07:08
+2011-02-01 21:37:03
+2011-02-01 22:07:03
+2011-02-01 22:37:03
+2011-02-01 23:07:02
+2011-02-01 23:37:03
+2011-02-02 00:07:02
+2011-02-02 00:37:03
+2011-02-02 01:07:02
+2011-02-02 01:37:02
+2011-02-02 02:07:03
+2011-02-02 02:37:02
+2011-02-02 03:07:03
+2011-02-02 03:37:03
+2011-02-02 04:07:02
+2011-02-02 04:37:03
+2011-02-02 05:07:02
+2011-02-02 05:37:03
+2011-02-02 06:07:03
+2011-02-02 06:37:02
+2011-02-02 07:07:03
+2011-02-02 07:37:03
+2011-02-02 08:07:03
+2011-02-02 08:37:02
+2011-02-02 09:07:03
+2011-02-02 09:37:03
+2011-02-02 10:07:03
+2011-02-02 10:37:02
+2011-02-02 11:07:02
+2011-02-02 11:37:02
+2011-02-02 12:07:03
+2011-02-02 12:37:03
+2011-02-02 13:07:02
+2011-02-02 13:37:03
+2011-02-02 14:07:02
+2011-02-02 14:37:03
+2011-02-02 15:07:03
+2011-02-02 15:37:02
+2011-02-02 16:07:03
+2011-02-02 16:37:03
+2011-02-02 17:07:03
+2011-02-02 17:37:03
+2011-02-02 18:07:03
+2011-02-02 18:37:02
+2011-02-02 19:07:03
+2011-02-02 19:37:03
+2011-02-02 20:07:03
+2011-02-02 20:37:03
+2011-02-02 21:07:03
+2011-02-02 21:37:03
+2011-02-02 22:07:03
+2011-02-02 22:37:03
+2011-02-02 23:07:03
+2011-02-02 23:37:03
+2011-02-03 00:07:03
+2011-02-03 00:37:02
+2011-02-03 01:07:03
+2011-02-03 01:37:02
+2011-02-03 02:07:03
+2011-02-03 02:37:02
+2011-02-03 03:07:02
+2011-02-03 03:37:02
+2011-02-03 04:07:03
+2011-02-03 04:37:02
+2011-02-03 05:07:02
+2011-02-03 05:37:03
+2011-02-03 06:07:03
+2011-02-03 06:37:03
+2011-02-03 07:07:02
+2011-02-03 07:37:02
+2011-02-03 08:07:02
+2011-02-03 08:37:02
+2011-02-03 09:07:03
+2011-02-03 09:37:03
+2011-02-03 10:07:02
+2011-02-03 10:37:03
+2011-02-03 11:07:02
+2011-02-03 11:37:03
+2011-02-03 12:07:02
+2011-02-03 12:37:02
+2011-02-03 13:07:03
+2011-02-03 13:37:03
+2011-02-03 14:07:02
+2011-02-03 14:37:03
+2011-02-03 15:07:02
+2011-02-03 15:37:02
+2011-02-03 16:07:03
+2011-02-03 16:37:03
+2011-02-03 17:07:02
+2011-02-03 17:37:03
+2011-02-03 18:07:03
+2011-02-03 18:37:03
+2011-02-03 19:07:02
+2011-02-03 19:37:03
+2011-02-03 20:07:03
+2011-02-03 20:37:03
+2011-02-03 21:07:03
+2011-02-03 21:37:03
+2011-02-03 22:07:03
+2011-02-03 22:37:03
+2011-02-03 23:07:02
+2011-02-03 23:37:03
+2011-02-04 00:07:02
+2011-02-04 00:37:03
+2011-02-04 01:07:03
+2011-02-04 01:37:02
+2011-02-04 02:07:03
+2011-02-04 02:37:03
+2011-02-04 03:07:03
+2011-02-04 03:37:02
+2011-02-04 04:07:02
+2011-02-04 04:37:03
+2011-02-04 05:07:02
+2011-02-04 05:37:03
+2011-02-04 06:07:03
+2011-02-04 06:37:03
+2011-02-04 07:07:03
+2011-02-04 07:37:03
+2011-02-04 08:07:03
+2011-02-04 08:37:03
+2011-02-04 09:07:02
+2011-02-04 09:37:03
+2011-02-04 10:07:03
+2011-02-04 10:37:03
+2011-02-04 11:07:03
+2011-02-04 11:37:02
+2011-02-04 12:07:03
+2011-02-04 12:37:02
+2011-02-04 13:07:03
+2011-02-04 13:37:03
+2011-02-04 14:07:03
+2011-02-04 14:37:02
+2011-02-04 15:07:03
+2011-02-04 15:37:03
+2011-02-04 16:07:03
+2011-02-04 16:37:02
+2011-02-04 17:07:03
+2011-02-04 17:37:03
+2011-02-04 18:07:03
+2011-02-04 18:37:03
+2011-02-04 19:07:03
+2011-02-04 19:37:03
+2011-02-04 20:07:03
+2011-02-04 20:37:03
+2011-02-04 21:07:02
+2011-02-04 21:37:02
+2011-02-04 22:07:02
+2011-02-04 22:37:03
+2011-02-04 23:07:03
+2011-02-04 23:37:03
+2011-02-05 00:07:03
+2011-02-05 00:37:02
+2011-02-05 01:07:03
+2011-02-05 01:37:03
+2011-02-05 02:07:03
+2011-02-05 02:37:02
+2011-02-05 03:07:03
+2011-02-05 03:37:02
+2011-02-05 04:07:03
+2011-02-05 04:37:03
+2011-02-05 05:07:03
+2011-02-05 05:37:05
+2011-02-05 06:07:02
+2011-02-05 06:37:02
+2011-02-05 07:07:02
+2011-02-05 07:37:03
+2011-02-05 08:07:03
+2011-02-05 08:37:03
+2011-02-05 09:07:03
+2011-02-05 09:37:02
+2011-02-05 10:07:03
+2011-02-05 10:37:03
+2011-02-05 11:07:02
+2011-02-05 11:37:02
+2011-02-05 12:07:03
+2011-02-05 12:37:03
+2011-02-05 13:07:02
+2011-02-05 13:37:03
+2011-02-05 14:07:04
+2011-02-05 14:37:03
+2011-02-05 15:07:03
+2011-02-05 15:37:03
+2011-02-05 16:07:02
+2011-02-05 16:37:03
+2011-02-05 17:07:03
+2011-02-05 17:37:03
+2011-02-05 18:07:03
+2011-02-05 18:37:03
+2011-02-05 19:07:02
+2011-02-05 19:37:03
+2011-02-05 20:07:03
+2011-02-05 20:37:03
+2011-02-05 21:07:02
+2011-02-05 21:37:03
+2011-02-05 22:07:03
+2011-02-05 22:37:03
+2011-02-05 23:07:03
+2011-02-05 23:37:02
+2011-02-06 00:07:03
+2011-02-06 00:37:02
+2011-02-06 01:07:03
+2011-02-06 01:37:02
+2011-02-06 02:07:03
+2011-02-06 02:37:03
+2011-02-06 03:07:03
+2011-02-06 03:37:06
+2011-02-06 04:07:03
+2011-02-06 04:37:03
+2011-02-06 05:07:03
+2011-02-06 05:37:03
+2011-02-06 06:07:03
+2011-02-06 06:37:03
+2011-02-06 07:07:02
+2011-02-06 07:37:02
+2011-02-06 08:07:02
+2011-02-06 08:37:03
+2011-02-06 09:07:02
+2011-02-06 09:37:02
+2011-02-06 10:07:02
+2011-02-06 10:37:02
+2011-02-06 11:07:02
+2011-02-06 11:37:02
+2011-02-06 12:07:03
+2011-02-06 12:37:02
+2011-02-06 13:07:02
+2011-02-06 13:37:03
+2011-02-06 14:07:03
+2011-02-06 14:37:02
+2011-02-06 15:07:02
+2011-02-06 15:37:03
+2011-02-06 16:07:03
+2011-02-06 16:37:03
+2011-02-06 17:07:03
+2011-02-06 17:37:02
+2011-02-06 18:07:03
+2011-02-06 18:37:02
+2011-02-06 19:07:02
+2011-02-06 19:37:02
+2011-02-06 20:07:03
+2011-02-06 20:37:02
+2011-02-06 21:07:03
+2011-02-06 21:37:03
+2011-02-06 22:07:03
+2011-02-06 22:37:02
+2011-02-06 23:07:02
+2011-02-06 23:37:03
+2011-02-07 00:07:03
+2011-02-07 00:37:02
+2011-02-07 01:07:03
+2011-02-07 01:37:02
+2011-02-07 02:07:02
+2011-02-07 02:37:02
+2011-02-07 03:07:02
+2011-02-07 03:37:03
+2011-02-07 04:07:02
+2011-02-07 04:37:02
+2011-02-07 05:07:02
+2011-02-07 05:37:03
+2011-02-07 06:07:03
+2011-02-07 06:37:02
+2011-02-07 07:07:03
+2011-02-07 07:37:03
+2011-02-07 08:07:03
+2011-02-07 08:37:03
+2011-02-07 09:07:03
+2011-02-07 09:37:02
+2011-02-07 10:07:02
+2011-02-07 10:37:02
+2011-02-07 11:07:02
+2011-02-07 11:37:02
+2011-02-07 12:07:03
+2011-02-07 12:37:03
+2011-02-07 13:07:03
+2011-02-07 13:37:02
+2011-02-07 14:07:03
+2011-02-07 14:37:03
+2011-02-07 15:07:03
+2011-02-07 15:37:03
+2011-02-07 16:07:02
+2011-02-07 16:37:03
+2011-02-07 17:07:02
+2011-02-07 17:37:02
+2011-02-07 18:07:03
+2011-02-07 18:37:03
+2011-02-07 19:07:02
+2011-02-07 19:37:02
+2011-02-07 20:07:03
+2011-02-07 20:37:03
+2011-02-07 21:07:06
+2011-02-07 21:37:02
+2011-02-07 22:07:02
+2011-02-07 22:37:03
+2011-02-07 23:07:03
+2011-02-07 23:37:02
+2011-02-08 00:07:02
+2011-02-08 00:37:02
+2011-02-08 01:07:03
+2011-02-08 01:37:03
+2011-02-08 02:07:02
+2011-02-08 02:37:03
+2011-02-08 03:07:03
+2011-02-08 03:37:03
+2011-02-08 04:07:02
+2011-02-08 04:37:03
+2011-02-08 05:07:02
+2011-02-08 05:37:03
+2011-02-08 06:07:03
+2011-02-08 06:37:02
+2011-02-08 07:07:03
+2011-02-08 07:37:03
+2011-02-08 08:07:02
+2011-02-08 08:37:03
+2011-02-08 09:07:03
+2011-02-08 09:37:02
+2011-02-08 10:07:02
+2011-02-08 10:37:03
+2011-02-08 11:07:02
+2011-02-08 11:37:02
+2011-02-08 12:07:02
+2011-02-08 12:37:02
+2011-02-08 13:07:03
+2011-02-08 13:37:02
+2011-02-08 14:07:03
+2011-02-08 14:37:02
+2011-02-08 15:07:03
+2011-02-08 15:37:02
+2011-02-08 16:07:03
+2011-02-08 16:37:03
+2011-02-08 17:07:03
+2011-02-08 17:37:03
+2011-02-08 18:07:03
+2011-02-08 18:37:02
+2011-02-08 19:07:03
+2011-02-08 19:37:03
+2011-02-08 20:07:03
+2011-02-08 20:37:03
+2011-02-08 21:07:03
+2011-02-08 21:37:03
+2011-02-08 22:07:03
+2011-02-08 22:37:05
+2011-02-08 23:07:03
+2011-02-08 23:37:02
+2011-02-09 00:07:02
+2011-02-09 00:37:02
+2011-02-09 01:07:02
+2011-02-09 01:37:03
+2011-02-09 02:07:02
+2011-02-09 02:37:03
+2011-02-09 03:07:03
+2011-02-09 03:37:03
+2011-02-09 04:07:03
+2011-02-09 04:37:03
+2011-02-09 05:07:02
+2011-02-09 05:37:03
+2011-02-09 06:07:03
+2011-02-09 06:37:03
+2011-02-09 07:07:03
+2011-02-09 07:37:03
+2011-02-09 08:07:02
+2011-02-09 08:37:02
+2011-02-09 09:07:03
+2011-02-09 09:37:03
+2011-02-09 10:07:03
+2011-02-09 10:37:02
+2011-02-09 11:07:02
+2011-02-09 11:37:03
+2011-02-09 12:07:03
+2011-02-09 12:37:03
+2011-02-09 13:07:02
+2011-02-09 13:37:02
+2011-02-09 14:07:03
+2011-02-09 14:37:02
+2011-02-09 15:07:03
+2011-02-09 15:37:02
+2011-02-09 16:07:03
+2011-02-09 16:37:02
+2011-02-09 17:07:02
+2011-02-09 17:37:03
+2011-02-09 18:07:03
+2011-02-09 18:37:03
+2011-02-09 19:07:03
+2011-02-09 19:37:03
+2011-02-09 20:07:03
+2011-02-09 20:37:02
+2011-02-09 21:07:02
+2011-02-09 21:37:03
+2011-02-09 22:07:03
+2011-02-09 22:37:03
+2011-02-09 23:07:02
+2011-02-09 23:37:03
+2011-02-10 00:07:03
+2011-02-10 00:37:03
+2011-02-10 01:07:02
+2011-02-10 01:37:02
+2011-02-10 02:07:03
+2011-02-10 02:37:02
+2011-02-10 03:07:02
+2011-02-10 03:37:03
+2011-02-10 04:07:03
+2011-02-10 04:37:02
+2011-02-10 05:07:03
+2011-02-10 05:37:03
+2011-02-10 06:07:03
+2011-02-10 06:37:02
+2011-02-10 07:07:02
+2011-02-10 07:37:03
+2011-02-10 08:07:03
+2011-02-10 08:37:03
+2011-02-10 09:07:02
+2011-02-10 09:37:02
+2011-02-10 10:07:02
+2011-02-10 10:37:03
+2011-02-10 11:07:03
+2011-02-10 11:37:03
+2011-02-10 12:07:03
+2011-02-10 12:37:02
+2011-02-10 13:07:03
+2011-02-10 13:37:02
+2011-02-10 14:07:03
+2011-02-10 14:37:03
+2011-02-10 15:07:02
+2011-02-10 15:37:03
+2011-02-10 16:07:03
+2011-02-10 16:37:03
+2011-02-10 17:07:02
+2011-02-10 17:37:03
+2011-02-10 18:07:02
+2011-02-10 18:37:03
+2011-02-10 19:07:02
+2011-02-10 19:37:02
+2011-02-10 20:07:03
+2011-02-10 20:37:03
+2011-02-10 21:07:03
+2011-02-10 21:37:03
+2011-02-10 22:07:03
+2011-02-10 22:37:03
+2011-02-10 23:07:03
+2011-02-10 23:37:05
+2011-02-11 00:07:03
+2011-02-11 00:37:03
+2011-02-11 01:07:03
+2011-02-11 01:37:03
+2011-02-11 02:07:03
+2011-02-11 02:37:02
+2011-02-11 03:07:02
+2011-02-11 03:37:03
+2011-02-11 04:07:03
+2011-02-11 04:37:03
+2011-02-11 05:07:02
+2011-02-11 05:37:02
+2011-02-11 06:07:03
+2011-02-11 06:37:03
+2011-02-11 07:07:03
+2011-02-11 07:37:03
+2011-02-11 08:07:03
+2011-02-11 08:37:03
+2011-02-11 09:07:03
+2011-02-11 09:37:03
+2011-02-11 10:07:03
+2011-02-11 10:37:02
+2011-02-11 11:07:02
+2011-02-11 11:37:03
+2011-02-11 12:07:03
+2011-02-11 12:37:03
+2011-02-11 13:07:03
+2011-02-11 13:37:02
+2011-02-11 14:07:03
+2011-02-11 14:37:03
+2011-02-11 15:07:03
+2011-02-11 15:37:04
+2011-02-11 16:07:03
+2011-02-11 16:37:03
+2011-02-11 17:07:03
+2011-02-11 17:37:03
+2011-02-11 18:07:03
+2011-02-11 18:37:03
+2011-02-11 19:07:03
+2011-02-11 19:37:03
+2011-02-11 20:07:03
+2011-02-11 20:37:03
+2011-02-11 21:07:03
+2011-02-11 21:37:03
+2011-02-11 22:07:02
+2011-02-11 22:37:02
+2011-02-11 23:07:02
+2011-02-11 23:37:03
+2011-02-12 00:07:02
+2011-02-12 00:37:02
+2011-02-12 01:07:03
+2011-02-12 01:37:03
+2011-02-12 02:07:02
+2011-02-12 02:37:02
+2011-02-12 03:07:02
+2011-02-12 03:37:03
+2011-02-12 04:07:02
+2011-02-12 04:37:03
+2011-02-12 05:07:02
+2011-02-12 05:37:02
+2011-02-12 06:07:03
+2011-02-12 06:37:03
+2011-02-12 07:07:02
+2011-02-12 07:37:03
+2011-02-12 08:07:03
+2011-02-12 08:37:03
+2011-02-12 09:07:03
+2011-02-12 09:37:03
+2011-02-12 10:07:02
+2011-02-12 10:37:03
+2011-02-12 11:07:03
+2011-02-12 11:37:03
+2011-02-12 12:07:03
+2011-02-12 12:37:03
+2011-02-12 13:07:03
+2011-02-12 13:37:03
+2011-02-12 14:07:03
+2011-02-12 14:37:02
+2011-02-12 15:07:02
+2011-02-12 15:37:03
+2011-02-12 16:07:03
+2011-02-12 16:37:03
+2011-02-12 17:07:03
+2011-02-12 17:37:03
+2011-02-12 18:07:03
+2011-02-12 18:37:02
+2011-02-12 19:07:02
+2011-02-12 19:37:03
+2011-02-12 20:07:03
+2011-02-12 20:37:03
+2011-02-12 21:07:03
+2011-02-12 21:37:03
+2011-02-12 22:07:03
+2011-02-12 22:37:03
+2011-02-12 23:07:03
+2011-02-12 23:37:03
+2011-02-13 00:07:02
+2011-02-13 00:37:03
+2011-02-13 01:07:03
+2011-02-13 01:37:02
+2011-02-13 02:07:02
+2011-02-13 02:37:03
+2011-02-13 03:07:02
+2011-02-13 03:37:03
+2011-02-13 04:07:03
+2011-02-13 04:37:03
+2011-02-13 05:07:03
+2011-02-13 05:37:03
+2011-02-13 06:07:03
+2011-02-13 06:37:03
+2011-02-13 07:07:02
+2011-02-13 07:37:02
+2011-02-13 08:07:03
+2011-02-13 08:37:03
+2011-02-13 09:07:03
+2011-02-13 09:37:03
+2011-02-13 10:07:03
+2011-02-13 10:37:02
+2011-02-13 11:07:03
+2011-02-13 11:37:03
+2011-02-13 12:07:03
+2011-02-13 12:37:02
+2011-02-13 13:07:02
+2011-02-13 13:37:03
+2011-02-13 14:07:03
+2011-02-13 14:37:02
+2011-02-13 15:07:02
+2011-02-13 15:37:03
+2011-02-13 16:07:04
+2011-02-13 16:37:03
+2011-02-13 17:07:03
+2011-02-13 17:37:03
+2011-02-13 18:07:02
+2011-02-13 18:37:03
+2011-02-13 19:07:03
+2011-02-13 19:37:03
+2011-02-13 20:07:03
+2011-02-13 20:37:03
+2011-02-13 21:07:03
+2011-02-13 21:37:02
+2011-02-13 22:07:03
+2011-02-13 22:37:03
+2011-02-13 23:07:02
+2011-02-13 23:37:02
+2011-02-14 00:07:03
+2011-02-14 00:37:02
+2011-02-14 01:07:03
+2011-02-14 01:37:02
+2011-02-14 02:07:03
+2011-02-14 02:37:03
+2011-02-14 03:07:03
+2011-02-14 03:37:03
+2011-02-14 04:07:02
+2011-02-14 04:37:02
+2011-02-14 05:07:02
+2011-02-14 05:37:03
+2011-02-14 06:07:03
+2011-02-14 06:37:02
+2011-02-14 07:07:02
+2011-02-14 07:37:02
+2011-02-14 08:07:02
+2011-02-14 08:37:03
+2011-02-14 09:07:02
+2011-02-14 09:37:02
+2011-02-14 10:07:03
+2011-02-14 10:37:03
+2011-02-14 11:07:03
+2011-02-14 11:37:02
+2011-02-14 12:07:03
+2011-02-14 12:37:03
+2011-02-14 13:07:03
+2011-02-14 13:37:02
+2011-02-14 14:07:02
+2011-02-14 14:37:03
+2011-02-14 15:07:03
+2011-02-14 15:37:02
+2011-02-14 16:07:02
+2011-02-14 16:37:03
+2011-02-14 17:07:02
+2011-02-14 17:37:03
+2011-02-14 18:07:03
+2011-02-14 18:37:03
+2011-02-14 19:07:02
+2011-02-14 19:37:03
+2011-02-14 20:07:03
+2011-02-14 20:37:03
+2011-02-14 21:07:03
+2011-02-14 21:37:03
+2011-02-14 22:07:03
+2011-02-14 22:37:03
+2011-02-14 23:07:03
+2011-02-14 23:37:02
+2011-02-15 00:07:03
+2011-02-15 00:37:02
+2011-02-15 01:07:03
+2011-02-15 01:37:03
+2011-02-15 02:07:03
+2011-02-15 02:37:03
+2011-02-15 03:07:03
+2011-02-15 03:37:02
+2011-02-15 04:07:03
+2011-02-15 04:37:03
+2011-02-15 05:07:02
+2011-02-15 05:37:02
+2011-02-15 06:07:02
+2011-02-15 06:37:02
+2011-02-15 07:07:03
+2011-02-15 07:37:02
+2011-02-15 08:07:03
+2011-02-15 08:37:02
+2011-02-15 09:07:02
+2011-02-15 09:37:03
+2011-02-15 10:07:02
+2011-02-15 10:37:02
+2011-02-15 11:07:03
+2011-02-15 11:37:03
+2011-02-15 12:07:03
+2011-02-15 12:37:02
+2011-02-15 13:07:02
+2011-02-15 13:37:02
+2011-02-15 14:07:03
+2011-02-15 14:37:02
+2011-02-15 15:07:02
+2011-02-15 15:37:02
+2011-02-15 16:07:02
+2011-02-15 16:37:02
+2011-02-15 17:07:03
+2011-02-15 17:37:03
+2011-02-15 18:07:03
+2011-02-15 18:37:03
+2011-02-15 19:07:03
+2011-02-15 19:37:02
+2011-02-15 20:07:03
+2011-02-15 20:37:03
+2011-02-15 21:07:03
+2011-02-15 21:37:03
+2011-02-15 22:07:03
+2011-02-15 22:37:03
+2011-02-15 23:07:02
+2011-02-15 23:37:02
+2011-02-16 00:07:03
+2011-02-16 00:37:02
+2011-02-16 01:07:04
+2011-02-16 01:37:02
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits