
commit d5b32d86245a323e0e6b6fde0899d14557438490 Author: Karsten Loesing <karsten.loesing@gmx.net> Date: Sat Nov 21 20:46:08 2015 +0100 Parse flag thresholds in bridge network statuses. Also parse the "ignoring-advertised-bws" flag threshold in votes that was added while we were not looking. Implements #17617. --- CHANGELOG.md | 8 ++ .../torproject/descriptor/BridgeNetworkStatus.java | 47 ++++++ .../descriptor/RelayNetworkStatusVote.java | 6 + .../descriptor/impl/BridgeNetworkStatusImpl.java | 102 +++++++++++++ .../impl/RelayNetworkStatusVoteImpl.java | 8 ++ .../descriptor/impl/BridgeNetworkStatusTest.java | 151 ++++++++++++++++++++ 6 files changed, 322 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 437f2e6..d9eba21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# Changes in version 1.x.x - 201x-xx-xx + + * Medium changes + - Parse flag thresholds in bridge network statuses, and parse the + "ignoring-advertised-bws" flag threshold in relay network status + votes. + + # Changes in version 1.0.0 - 2015-12-05 * Major changes diff --git a/src/org/torproject/descriptor/BridgeNetworkStatus.java b/src/org/torproject/descriptor/BridgeNetworkStatus.java index 296cd22..1a28ebf 100644 --- a/src/org/torproject/descriptor/BridgeNetworkStatus.java +++ b/src/org/torproject/descriptor/BridgeNetworkStatus.java @@ -9,6 +9,53 @@ public interface BridgeNetworkStatus extends Descriptor { /* Return the published time in milliseconds. */ public long getPublishedMillis(); + /* Return the minimum uptime in seconds that this authority requires for + * assigning the Stable flag, or -1 if the authority doesn't report this + * value. */ + public long getStableUptime(); + + /* Return the minimum MTBF (mean time between failure) that this + * authority requires for assigning the Stable flag, or -1 if the + * authority doesn't report this value. */ + public long getStableMtbf(); + + /* Return the minimum bandwidth that this authority requires for + * assigning the Fast flag, or -1 if the authority doesn't report this + * value. */ + public long getFastBandwidth(); + + /* Return the minimum WFU (weighted fractional uptime) in percent that + * this authority requires for assigning the Guard flag, or -1.0 if the + * authority doesn't report this value. */ + public double getGuardWfu(); + + /* Return the minimum weighted time in seconds that this authority needs + * to know about a relay before assigning the Guard flag, or -1 if the + * authority doesn't report this information. */ + public long getGuardTk(); + + /* Return the minimum bandwidth that this authority requires for + * assigning the Guard flag if exits can be guards, or -1 if the + * authority doesn't report this value. */ + public long getGuardBandwidthIncludingExits(); + + /* Return the minimum bandwidth that this authority requires for + * assigning the Guard flag if exits can not be guards, or -1 if the + * authority doesn't report this value. */ + public long getGuardBandwidthExcludingExits(); + + /* Return 1 if the authority has measured enough MTBF info to use the + * MTBF requirement instead of the uptime requirement for assigning the + * Stable flag, 0 if not, or -1 if the authority doesn't report this + * information. */ + public int getEnoughMtbfInfo(); + + /* Return 1 if the authority has enough measured bandwidths that it'll + * ignore the advertised bandwidth claims of routers without measured + * bandwidth, 0 if not, or -1 if the authority doesn't report this + * information. */ + public int getIgnoringAdvertisedBws(); + /* Return status entries, one for each contained bridge. */ public SortedMap<String, NetworkStatusEntry> getStatusEntries(); } diff --git a/src/org/torproject/descriptor/RelayNetworkStatusVote.java b/src/org/torproject/descriptor/RelayNetworkStatusVote.java index aeab268..bf4ea6b 100644 --- a/src/org/torproject/descriptor/RelayNetworkStatusVote.java +++ b/src/org/torproject/descriptor/RelayNetworkStatusVote.java @@ -85,6 +85,12 @@ public interface RelayNetworkStatusVote extends Descriptor { * information. */ public int getEnoughMtbfInfo(); + /* Return 1 if the authority has enough measured bandwidths that it'll + * ignore the advertised bandwidth claims of routers without measured + * bandwidth, 0 if not, or -1 if the authority doesn't report this + * information. */ + public int getIgnoringAdvertisedBws(); + /* Return consensus parameters. */ public SortedMap<String, Integer> getConsensusParams(); diff --git a/src/org/torproject/descriptor/impl/BridgeNetworkStatusImpl.java b/src/org/torproject/descriptor/impl/BridgeNetworkStatusImpl.java index 5935c86..bddf5ab 100644 --- a/src/org/torproject/descriptor/impl/BridgeNetworkStatusImpl.java +++ b/src/org/torproject/descriptor/impl/BridgeNetworkStatusImpl.java @@ -6,7 +6,9 @@ import org.torproject.descriptor.DescriptorParseException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Map; import java.util.Scanner; +import java.util.SortedMap; import java.util.TimeZone; import org.torproject.descriptor.BridgeNetworkStatus; @@ -52,6 +54,20 @@ public class BridgeNetworkStatusImpl extends NetworkStatusImpl protected void parseHeader(byte[] headerBytes) throws DescriptorParseException { + /* Initialize flag-thresholds values here for the case that the status + * doesn't contain those values. Initializing them in the constructor + * or when declaring variables wouldn't work, because those parts are + * evaluated later and would overwrite everything we parse here. */ + this.stableUptime = -1L; + this.stableMtbf = -1L; + this.fastBandwidth = -1L; + this.guardWfu = -1.0; + this.guardTk = -1L; + this.guardBandwidthIncludingExits = -1L; + this.guardBandwidthExcludingExits = -1L; + this.enoughMtbfInfo = -1; + this.ignoringAdvertisedBws = -1; + Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n"); while (s.hasNext()) { String line = s.next(); @@ -59,6 +75,8 @@ public class BridgeNetworkStatusImpl extends NetworkStatusImpl String keyword = parts[0]; if (keyword.equals("published")) { this.parsePublishedLine(line, parts); + } else if (keyword.equals("flag-thresholds")) { + this.parseFlagThresholdsLine(line, parts); } else if (this.failUnrecognizedDescriptorLines) { throw new DescriptorParseException("Unrecognized line '" + line + "' in bridge network status."); @@ -77,6 +95,45 @@ public class BridgeNetworkStatusImpl extends NetworkStatusImpl 1, 2); } + private void parseFlagThresholdsLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length < 2) { + throw new DescriptorParseException("No flag thresholds in line '" + + line + "'."); + } + SortedMap<String, String> flagThresholds = + ParseHelper.parseKeyValueStringPairs(line, parts, 1, "="); + try { + for (Map.Entry<String, String> e : flagThresholds.entrySet()) { + if (e.getKey().equals("stable-uptime")) { + this.stableUptime = Long.parseLong(e.getValue()); + } else if (e.getKey().equals("stable-mtbf")) { + this.stableMtbf = Long.parseLong(e.getValue()); + } else if (e.getKey().equals("fast-speed")) { + this.fastBandwidth = Long.parseLong(e.getValue()); + } else if (e.getKey().equals("guard-wfu")) { + this.guardWfu = Double.parseDouble(e.getValue(). + replaceAll("%", "")); + } else if (e.getKey().equals("guard-tk")) { + this.guardTk = Long.parseLong(e.getValue()); + } else if (e.getKey().equals("guard-bw-inc-exits")) { + this.guardBandwidthIncludingExits = + Long.parseLong(e.getValue()); + } else if (e.getKey().equals("guard-bw-exc-exits")) { + this.guardBandwidthExcludingExits = + Long.parseLong(e.getValue()); + } else if (e.getKey().equals("enough-mtbf")) { + this.enoughMtbfInfo = Integer.parseInt(e.getValue()); + } else if (e.getKey().equals("ignoring-advertised-bws")) { + this.ignoringAdvertisedBws = Integer.parseInt(e.getValue()); + } + } + } catch (NumberFormatException ex) { + throw new DescriptorParseException("Illegal value in line '" + + line + "'."); + } + } + protected void parseDirSource(byte[] dirSourceBytes) throws DescriptorParseException { throw new DescriptorParseException("No directory source expected in " @@ -99,5 +156,50 @@ public class BridgeNetworkStatusImpl extends NetworkStatusImpl public long getPublishedMillis() { return this.publishedMillis; } + + private long stableUptime; + public long getStableUptime() { + return this.stableUptime; + } + + private long stableMtbf; + public long getStableMtbf() { + return this.stableMtbf; + } + + private long fastBandwidth; + public long getFastBandwidth() { + return this.fastBandwidth; + } + + private double guardWfu; + public double getGuardWfu() { + return this.guardWfu; + } + + private long guardTk; + public long getGuardTk() { + return this.guardTk; + } + + private long guardBandwidthIncludingExits; + public long getGuardBandwidthIncludingExits() { + return this.guardBandwidthIncludingExits; + } + + private long guardBandwidthExcludingExits; + public long getGuardBandwidthExcludingExits() { + return this.guardBandwidthExcludingExits; + } + + private int enoughMtbfInfo; + public int getEnoughMtbfInfo() { + return this.enoughMtbfInfo; + } + + private int ignoringAdvertisedBws; + public int getIgnoringAdvertisedBws() { + return this.ignoringAdvertisedBws; + } } diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java index f43733e..410c2f1 100644 --- a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java +++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java @@ -72,6 +72,7 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl this.guardBandwidthIncludingExits = -1L; this.guardBandwidthExcludingExits = -1L; this.enoughMtbfInfo = -1; + this.ignoringAdvertisedBws = -1; Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n"); boolean skipCrypto = false; /* TODO Parse crypto parts. */ @@ -276,6 +277,8 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl Long.parseLong(e.getValue()); } else if (e.getKey().equals("enough-mtbf")) { this.enoughMtbfInfo = Integer.parseInt(e.getValue()); + } else if (e.getKey().equals("ignoring-advertised-bws")) { + this.ignoringAdvertisedBws = Integer.parseInt(e.getValue()); } } } catch (NumberFormatException ex) { @@ -542,6 +545,11 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl return this.enoughMtbfInfo; } + private int ignoringAdvertisedBws; + public int getIgnoringAdvertisedBws() { + return this.ignoringAdvertisedBws; + } + private SortedMap<String, Integer> consensusParams; public SortedMap<String, Integer> getConsensusParams() { return this.consensusParams == null ? null: diff --git a/test/org/torproject/descriptor/impl/BridgeNetworkStatusTest.java b/test/org/torproject/descriptor/impl/BridgeNetworkStatusTest.java new file mode 100644 index 0000000..c8e95bf --- /dev/null +++ b/test/org/torproject/descriptor/impl/BridgeNetworkStatusTest.java @@ -0,0 +1,151 @@ +/* Copyright 2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.torproject.descriptor.BridgeNetworkStatus; +import org.torproject.descriptor.DescriptorParseException; + +/* Test parsing of bridge network statuses. Some of the parsing code is + * already tested in the consensus/vote-parsing tests. */ +public class BridgeNetworkStatusTest { + + /* Helper class to build a bridge network status based on default data + * and modifications requested by test methods. */ + private static class StatusBuilder { + private String fileName = "20151121-173936-" + + "4A0CCD2DDC7995083D73F5D667100C8A5831F16D"; + private static BridgeNetworkStatus + createWithFileName(String fileName) + throws DescriptorParseException { + StatusBuilder sb = new StatusBuilder(); + sb.fileName = fileName; + return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, + true); + } + private String publishedLine = "published 2015-11-21 17:39:36"; + private static BridgeNetworkStatus + createWithPublishedLine(String line) + throws DescriptorParseException { + StatusBuilder sb = new StatusBuilder(); + sb.publishedLine = line; + return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, + true); + } + private String flagThresholdsLine = "flag-thresholds " + + "stable-uptime=3105080 stable-mtbf=2450615 fast-speed=55000 " + + "guard-wfu=98.000% guard-tk=691200 guard-bw-inc-exits=337000 " + + "guard-bw-exc-exits=339000 enough-mtbf=1 " + + "ignoring-advertised-bws=0"; + private static BridgeNetworkStatus + createWithFlagThresholdsLine(String line) + throws DescriptorParseException { + StatusBuilder sb = new StatusBuilder(); + sb.flagThresholdsLine = line; + return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, + true); + } + private List<String> statusEntries = new ArrayList<String>(); + private String unrecognizedHeaderLine = null; + protected static BridgeNetworkStatus + createWithUnrecognizedHeaderLine(String line, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + StatusBuilder sb = new StatusBuilder(); + sb.unrecognizedHeaderLine = line; + return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, + failUnrecognizedDescriptorLines); + } + private String unrecognizedStatusEntryLine = null; + protected static BridgeNetworkStatus + createWithUnrecognizedStatusEntryLine(String line, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + StatusBuilder sb = new StatusBuilder(); + sb.unrecognizedStatusEntryLine = line; + return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, + failUnrecognizedDescriptorLines); + } + + private StatusBuilder() { + this.statusEntries.add("r Unnamed ABk0wg4j6BLCdZKleVtmNrfzJGI " + + "bh7gVU1Cz6+JG+7j4qGsF4prDi8 2015-11-21 15:46:25 " + + "10.153.163.200 443 0\ns Fast Running Stable Valid\n" + + "w Bandwidth=264\np reject 1-65535"); + } + private byte[] buildStatus() { + StringBuilder sb = new StringBuilder(); + this.appendHeader(sb); + this.appendStatusEntries(sb); + return sb.toString().getBytes(); + } + private void appendHeader(StringBuilder sb) { + if (this.publishedLine != null) { + sb.append(this.publishedLine + "\n"); + } + if (this.flagThresholdsLine != null) { + sb.append(this.flagThresholdsLine + "\n"); + } + if (this.unrecognizedHeaderLine != null) { + sb.append(this.unrecognizedHeaderLine + "\n"); + } + } + private void appendStatusEntries(StringBuilder sb) { + for (String statusEntry : this.statusEntries) { + sb.append(statusEntry + "\n"); + } + if (this.unrecognizedStatusEntryLine != null) { + sb.append(this.unrecognizedStatusEntryLine + "\n"); + } + } + } + + @Test() + public void testSampleStatus() throws DescriptorParseException { + StatusBuilder sb = new StatusBuilder(); + BridgeNetworkStatus status = + new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, true); + assertEquals(1448127576000L, status.getPublishedMillis()); + assertEquals(3105080L, status.getStableUptime()); + assertEquals(2450615L, status.getStableMtbf()); + assertEquals(55000L, status.getFastBandwidth()); + assertEquals(98.0, status.getGuardWfu(), 0.001); + assertEquals(691200L, status.getGuardTk()); + assertEquals(337000L, status.getGuardBandwidthIncludingExits()); + assertEquals(339000L, status.getGuardBandwidthExcludingExits()); + assertEquals(1, status.getEnoughMtbfInfo()); + assertEquals(0, status.getIgnoringAdvertisedBws()); + assertEquals(264, status.getStatusEntries().get( + "001934C20E23E812C27592A5795B6636B7F32462").getBandwidth()); + assertTrue(status.getUnrecognizedLines().isEmpty()); + } + + @Test() + public void testPublishedNoLine() throws DescriptorParseException { + BridgeNetworkStatus status = + StatusBuilder.createWithPublishedLine(null); + assertEquals(1448127576000L, status.getPublishedMillis()); + } + + @Test() + public void testFlagThresholdsNoLine() throws DescriptorParseException { + BridgeNetworkStatus status = + StatusBuilder.createWithFlagThresholdsLine(null); + assertEquals(-1L, status.getStableUptime()); + assertEquals(-1L, status.getStableMtbf()); + assertEquals(-1L, status.getFastBandwidth()); + assertEquals(-1.0, status.getGuardWfu(), 0.001); + assertEquals(-1L, status.getGuardTk()); + assertEquals(-1L, status.getGuardBandwidthIncludingExits()); + assertEquals(-1L, status.getGuardBandwidthExcludingExits()); + assertEquals(-1, status.getEnoughMtbfInfo()); + assertEquals(-1, status.getIgnoringAdvertisedBws()); + } +} +