def new_desc_event(self, event): # updates self._titleStats with updated values conn = torTools.getConn() if not conn.isAlive(): return # keep old values myFingerprint = conn.getInfo("fingerprint", None) if not self._titleStats or not myFingerprint or (event and myFingerprint in event.idlist): stats = [] bwRate = conn.getMyBandwidthRate() bwBurst = conn.getMyBandwidthBurst() bwObserved = conn.getMyBandwidthObserved() bwMeasured = conn.getMyBandwidthMeasured() labelInBytes = CONFIG["features.graph.bw.transferInBytes"] if bwRate and bwBurst: bwRateLabel = str_tools.get_size_label(bwRate, 1, False, labelInBytes) bwBurstLabel = str_tools.get_size_label(bwBurst, 1, False, labelInBytes) # if both are using rounded values then strip off the ".0" decimal if ".0" in bwRateLabel and ".0" in bwBurstLabel: bwRateLabel = bwRateLabel.replace(".0", "") bwBurstLabel = bwBurstLabel.replace(".0", "") stats.append("limit: %s/s" % bwRateLabel) stats.append("burst: %s/s" % bwBurstLabel) # Provide the observed bandwidth either if the measured bandwidth isn't # available or if the measured bandwidth is the observed (this happens # if there isn't yet enough bandwidth measurements). if bwObserved and (not bwMeasured or bwMeasured == bwObserved): stats.append("observed: %s/s" % str_tools.get_size_label(bwObserved, 1, False, labelInBytes)) elif bwMeasured: stats.append("measured: %s/s" % str_tools.get_size_label(bwMeasured, 1, False, labelInBytes)) self._titleStats = stats
def getHeaderLabel(self, width, isPrimary): avg = (self.primaryTotal if isPrimary else self.secondaryTotal) / max(1, self.tick) lastAmount = self.lastPrimary if isPrimary else self.lastSecondary if isPrimary: return "CPU (%0.1f%%, avg: %0.1f%%):" % (lastAmount, avg) else: # memory sizes are converted from MB to B before generating labels usageLabel = str_tools.get_size_label(lastAmount * 1048576, 1) avgLabel = str_tools.get_size_label(avg * 1048576, 1) return "Memory (%s, avg: %s):" % (usageLabel, avgLabel)
def getHeaderLabel(self, width, isPrimary): avg = (self.primaryTotal if isPrimary else self.secondaryTotal) / max( 1, self.tick) lastAmount = self.lastPrimary if isPrimary else self.lastSecondary if isPrimary: return "CPU (%0.1f%%, avg: %0.1f%%):" % (lastAmount, avg) else: # memory sizes are converted from MB to B before generating labels usageLabel = str_tools.get_size_label(lastAmount * 1048576, 1) avgLabel = str_tools.get_size_label(avg * 1048576, 1) return "Memory (%s, avg: %s):" % (usageLabel, avgLabel)
def _updateAccountingInfo(self): """ Updates mapping used for accounting info. This includes the following keys: status, resetTime, read, written, readLimit, writtenLimit Any failed lookups result in a mapping to an empty string. """ conn = torTools.getConn() queried = dict([(arg, "") for arg in ACCOUNTING_ARGS]) queried["status"] = conn.getInfo("accounting/hibernating", None) # provides a nicely formatted reset time endInterval = conn.getInfo("accounting/interval-end", None) if endInterval: # converts from gmt to local with respect to DST if time.localtime()[8]: tz_offset = time.altzone else: tz_offset = time.timezone sec = time.mktime(time.strptime( endInterval, "%Y-%m-%d %H:%M:%S")) - time.time() - tz_offset if CONFIG["features.graph.bw.accounting.isTimeLong"]: queried["resetTime"] = ", ".join( str_tools.get_time_labels(sec, True)) else: days = sec / 86400 sec %= 86400 hours = sec / 3600 sec %= 3600 minutes = sec / 60 sec %= 60 queried["resetTime"] = "%i:%02i:%02i:%02i" % (days, hours, minutes, sec) # number of bytes used and in total for the accounting period used = conn.getInfo("accounting/bytes", None) left = conn.getInfo("accounting/bytes-left", None) if used and left: usedComp, leftComp = used.split(" "), left.split(" ") read, written = int(usedComp[0]), int(usedComp[1]) readLeft, writtenLeft = int(leftComp[0]), int(leftComp[1]) queried["read"] = str_tools.get_size_label(read) queried["written"] = str_tools.get_size_label(written) queried["readLimit"] = str_tools.get_size_label(read + readLeft) queried["writtenLimit"] = str_tools.get_size_label(written + writtenLeft) self.accountingInfo = queried self.accountingLastUpdated = time.time()
def getHeaderLabel(self, width, isPrimary): graphType = "Download" if isPrimary else "Upload" stats = [""] # if wide then avg and total are part of the header, otherwise they're on # the x-axis if width * 2 > COLLAPSE_WIDTH: stats = [""] * 3 stats[1] = "- %s" % self._getAvgLabel(isPrimary) stats[2] = ", %s" % self._getTotalLabel(isPrimary) stats[0] = "%-14s" % ("%s/sec" % str_tools.get_size_label( (self.lastPrimary if isPrimary else self.lastSecondary) * 1024, 1, False, CONFIG["features.graph.bw.transferInBytes"])) # drops label's components if there's not enough space labeling = graphType + " (" + "".join(stats).strip() + "):" while len(labeling) >= width: if len(stats) > 1: del stats[-1] labeling = graphType + " (" + "".join(stats).strip() + "):" else: labeling = graphType + ":" break return labeling
def tutorial_example(): from stem.control import Controller from stem.util import str_tools # provides a mapping of observed bandwidth to the relay nicknames def get_bw_to_relay(): bw_to_relay = {} with Controller.from_port(control_port=9051) as controller: controller.authenticate() for desc in controller.get_server_descriptors(): if desc.exit_policy.is_exiting_allowed(): bw_to_relay.setdefault(desc.observed_bandwidth, []).append(desc.nickname) return bw_to_relay # prints the top fifteen relays bw_to_relay = get_bw_to_relay() count = 1 for bw_value in sorted(bw_to_relay.keys(), reverse=True): for nickname in bw_to_relay[bw_value]: print "%i. %s (%s/s)" % ( count, nickname, str_tools.get_size_label(bw_value, 2)) count += 1 if count > 15: return
def tutorial_example(): from stem.descriptor.remote import DescriptorDownloader from stem.util import str_tools # provides a mapping of observed bandwidth to the relay nicknames def get_bw_to_relay(): bw_to_relay = {} downloader = DescriptorDownloader() try: for desc in downloader.get_server_descriptors().run(): if desc.exit_policy.is_exiting_allowed(): bw_to_relay.setdefault(desc.observed_bandwidth, []).append(desc.nickname) except Exception as exc: print "Unable to retrieve the server descriptors: %s" % exc return bw_to_relay # prints the top fifteen relays bw_to_relay = get_bw_to_relay() count = 1 for bw_value in sorted(bw_to_relay.keys(), reverse = True): for nickname in bw_to_relay[bw_value]: print "%i. %s (%s/s)" % (count, nickname, str_tools.get_size_label(bw_value, 2)) count += 1 if count > 15: return
def tutorial_example(): from stem.control import Controller from stem.util import str_tools # provides a mapping of observed bandwidth to the relay nicknames def get_bw_to_relay(): bw_to_relay = {} with Controller.from_port(control_port = 9051) as controller: controller.authenticate() for desc in controller.get_server_descriptors(): if desc.exit_policy.is_exiting_allowed(): bw_to_relay.setdefault(desc.observed_bandwidth, []).append(desc.nickname) return bw_to_relay # prints the top fifteen relays bw_to_relay = get_bw_to_relay() count = 1 for bw_value in sorted(bw_to_relay.keys(), reverse = True): for nickname in bw_to_relay[bw_value]: print "%i. %s (%s/s)" % (count, nickname, str_tools.get_size_label(bw_value, 2)) count += 1 if count > 15: return
def _updateAccountingInfo(self): """ Updates mapping used for accounting info. This includes the following keys: status, resetTime, read, written, readLimit, writtenLimit Any failed lookups result in a mapping to an empty string. """ conn = torTools.getConn() queried = dict([(arg, "") for arg in ACCOUNTING_ARGS]) queried["status"] = conn.getInfo("accounting/hibernating", None) # provides a nicely formatted reset time endInterval = conn.getInfo("accounting/interval-end", None) if endInterval: # converts from gmt to local with respect to DST if time.localtime()[8]: tz_offset = time.altzone else: tz_offset = time.timezone sec = time.mktime(time.strptime(endInterval, "%Y-%m-%d %H:%M:%S")) - time.time() - tz_offset if CONFIG["features.graph.bw.accounting.isTimeLong"]: queried["resetTime"] = ", ".join(str_tools.get_time_labels(sec, True)) else: days = sec / 86400 sec %= 86400 hours = sec / 3600 sec %= 3600 minutes = sec / 60 sec %= 60 queried["resetTime"] = "%i:%02i:%02i:%02i" % (days, hours, minutes, sec) # number of bytes used and in total for the accounting period used = conn.getInfo("accounting/bytes", None) left = conn.getInfo("accounting/bytes-left", None) if used and left: usedComp, leftComp = used.split(" "), left.split(" ") read, written = int(usedComp[0]), int(usedComp[1]) readLeft, writtenLeft = int(leftComp[0]), int(leftComp[1]) queried["read"] = str_tools.get_size_label(read) queried["written"] = str_tools.get_size_label(written) queried["readLimit"] = str_tools.get_size_label(read + readLeft) queried["writtenLimit"] = str_tools.get_size_label(written + writtenLeft) self.accountingInfo = queried self.accountingLastUpdated = time.time()
def new_desc_event(self, event): # updates self._titleStats with updated values conn = torTools.getConn() if not conn.isAlive(): return # keep old values myFingerprint = conn.getInfo("fingerprint", None) if not self._titleStats or not myFingerprint or ( event and myFingerprint in event.idlist): stats = [] bwRate = conn.getMyBandwidthRate() bwBurst = conn.getMyBandwidthBurst() bwObserved = conn.getMyBandwidthObserved() bwMeasured = conn.getMyBandwidthMeasured() labelInBytes = CONFIG["features.graph.bw.transferInBytes"] if bwRate and bwBurst: bwRateLabel = str_tools.get_size_label(bwRate, 1, False, labelInBytes) bwBurstLabel = str_tools.get_size_label( bwBurst, 1, False, labelInBytes) # if both are using rounded values then strip off the ".0" decimal if ".0" in bwRateLabel and ".0" in bwBurstLabel: bwRateLabel = bwRateLabel.replace(".0", "") bwBurstLabel = bwBurstLabel.replace(".0", "") stats.append("limit: %s/s" % bwRateLabel) stats.append("burst: %s/s" % bwBurstLabel) # Provide the observed bandwidth either if the measured bandwidth isn't # available or if the measured bandwidth is the observed (this happens # if there isn't yet enough bandwidth measurements). if bwObserved and (not bwMeasured or bwMeasured == bwObserved): stats.append("observed: %s/s" % str_tools.get_size_label( bwObserved, 1, False, labelInBytes)) elif bwMeasured: stats.append("measured: %s/s" % str_tools.get_size_label( bwMeasured, 1, False, labelInBytes)) self._titleStats = stats
def test_get_size_label(self): """ Checks the get_size_label() function. """ # test the pydoc examples self.assertEquals('1 MB', str_tools.get_size_label(2000000)) self.assertEquals('1.02 KB', str_tools.get_size_label(1050, 2)) self.assertEquals('1.025 Kilobytes', str_tools.get_size_label(1050, 3, True)) self.assertEquals('0 B', str_tools.get_size_label(0)) self.assertEquals('0 Bytes', str_tools.get_size_label(0, is_long = True)) self.assertEquals('0.00 B', str_tools.get_size_label(0, 2)) self.assertEquals('-10 B', str_tools.get_size_label(-10)) self.assertEquals('80 b', str_tools.get_size_label(10, is_bytes = False)) self.assertEquals('-1 MB', str_tools.get_size_label(-2000000)) # checking that we round down self.assertEquals('23.43 Kb', str_tools.get_size_label(3000, 2, is_bytes = False)) self.assertRaises(TypeError, str_tools.get_size_label, None) self.assertRaises(TypeError, str_tools.get_size_label, 'hello world')
def test_mirror_mirror_on_the_wall(self): from stem.descriptor.server_descriptor import RelayDescriptor from stem.descriptor.reader import DescriptorReader from stem.util import str_tools exit_descriptor = mocking.get_relay_server_descriptor({ 'router': 'speedyexit 149.255.97.109 9001 0 0' }, content = True).replace('reject *:*', 'accept *:*') exit_descriptor = mocking.sign_descriptor_content(exit_descriptor) exit_descriptor = RelayDescriptor(exit_descriptor) reader_wrapper = mocking.get_object(DescriptorReader, { '__enter__': lambda x: x, '__exit__': mocking.no_op(), '__iter__': mocking.return_value(iter(( exit_descriptor, mocking.get_relay_server_descriptor(), # non-exit exit_descriptor, exit_descriptor, ))) }) # provides a mapping of observed bandwidth to the relay nicknames def get_bw_to_relay(): bw_to_relay = {} with reader_wrapper as reader: for desc in reader: if desc.exit_policy.is_exiting_allowed(): bw_to_relay.setdefault(desc.observed_bandwidth, []).append(desc.nickname) return bw_to_relay # prints the top fifteen relays bw_to_relay = get_bw_to_relay() count = 1 for bw_value in sorted(bw_to_relay.keys(), reverse = True): for nickname in bw_to_relay[bw_value]: expected_line = "%i. speedyexit (102.13 KB/s)" % count printed_line = "%i. %s (%s/s)" % (count, nickname, str_tools.get_size_label(bw_value, 2)) self.assertEqual(expected_line, printed_line) count += 1 if count > 15: return self.assertEqual(4, count)
def _getValue(self): """ Provides the current value of the configuration entry, taking advantage of the torTools caching to effectively query the accurate value. This uses the value's type to provide a user friendly representation if able. """ confValue = ", ".join(torTools.getConn().getOption(self.get(Field.OPTION), [], True)) # provides nicer values for recognized types if not confValue: confValue = "<none>" elif self.get(Field.TYPE) == "Boolean" and confValue in ("0", "1"): confValue = "False" if confValue == "0" else "True" elif self.get(Field.TYPE) == "DataSize" and confValue.isdigit(): confValue = str_tools.get_size_label(int(confValue)) elif self.get(Field.TYPE) == "TimeInterval" and confValue.isdigit(): confValue = str_tools.get_time_label(int(confValue), is_long = True) return confValue
def _getValue(self): """ Provides the current value of the configuration entry, taking advantage of the torTools caching to effectively query the accurate value. This uses the value's type to provide a user friendly representation if able. """ confValue = ", ".join(torTools.getConn().getOption(self.get(Field.OPTION), [], True)) # provides nicer values for recognized types if not confValue: confValue = "<none>" elif self.get(Field.TYPE) == "Boolean" and confValue in ("0", "1"): confValue = "False" if confValue == "0" else "True" elif self.get(Field.TYPE) == "DataSize" and confValue.isdigit(): confValue = str_tools.get_size_label(int(confValue)) elif self.get(Field.TYPE) == "TimeInterval" and confValue.isdigit(): confValue = str_tools.get_time_label(int(confValue), is_long=True) return confValue
def getHeaderLabel(self, width, isPrimary): graphType = "Download" if isPrimary else "Upload" stats = [""] # if wide then avg and total are part of the header, otherwise they're on # the x-axis if width * 2 > COLLAPSE_WIDTH: stats = [""] * 3 stats[1] = "- %s" % self._getAvgLabel(isPrimary) stats[2] = ", %s" % self._getTotalLabel(isPrimary) stats[0] = "%-14s" % ("%s/sec" % str_tools.get_size_label((self.lastPrimary if isPrimary else self.lastSecondary) * 1024, 1, False, CONFIG["features.graph.bw.transferInBytes"])) # drops label's components if there's not enough space labeling = graphType + " (" + "".join(stats).strip() + "):" while len(labeling) >= width: if len(stats) > 1: del stats[-1] labeling = graphType + " (" + "".join(stats).strip() + "):" else: labeling = graphType + ":" break return labeling
def _getTotalLabel(self, isPrimary): total = self.primaryTotal if isPrimary else self.secondaryTotal total += self.initialPrimaryTotal if isPrimary else self.initialSecondaryTotal return "total: %s" % str_tools.get_size_label(total * 1024, 1)
def _getAvgLabel(self, isPrimary): total = self.primaryTotal if isPrimary else self.secondaryTotal total += self.prepopulatePrimaryTotal if isPrimary else self.prepopulateSecondaryTotal return "avg: %s/sec" % str_tools.get_size_label((total / max(1, self.tick + self.prepopulateTicks)) * 1024, 1, False, CONFIG["features.graph.bw.transferInBytes"])
def validate(contents=None): """ Performs validation on the given torrc contents, providing back a listing of (line number, issue, msg) tuples for issues found. If the issue occures on a multiline torrc entry then the line number is for the last line of the entry. Arguments: contents - torrc contents """ conn = torTools.getConn() customOptions = getCustomOptions() issuesFound, seenOptions = [], [] # Strips comments and collapses multiline multi-line entries, for more # information see: # https://trac.torproject.org/projects/tor/ticket/1929 strippedContents, multilineBuffer = [], "" for line in _stripComments(contents): if not line: strippedContents.append("") else: line = multilineBuffer + line multilineBuffer = "" if line.endswith("\\"): multilineBuffer = line[:-1] strippedContents.append("") else: strippedContents.append(line.strip()) for lineNumber in range(len(strippedContents) - 1, -1, -1): lineText = strippedContents[lineNumber] if not lineText: continue lineComp = lineText.split(None, 1) if len(lineComp) == 2: option, value = lineComp else: option, value = lineText, "" # Tor is case insensetive when parsing its torrc. This poses a bit of an # issue for us because we want all of our checks to be case insensetive # too but also want messages to match the normal camel-case conventions. # # Using the customOptions to account for this. It contains the tor reported # options (camel case) and is either a matching set or the following defaut # value check will fail. Hence using that hash to correct the case. # # TODO: when refactoring for stem make this less confusing... for customOpt in customOptions: if customOpt.lower() == option.lower(): option = customOpt break # if an aliased option then use its real name if option in CONFIG["torrc.alias"]: option = CONFIG["torrc.alias"][option] # most parameters are overwritten if defined multiple times if option in seenOptions and not option in getMultilineParameters(): issuesFound.append((lineNumber, ValidationError.DUPLICATE, option)) continue else: seenOptions.append(option) # checks if the value isn't necessary due to matching the defaults if not option in customOptions: issuesFound.append( (lineNumber, ValidationError.IS_DEFAULT, option)) # replace aliases with their recognized representation if option in CONFIG["torrc.alias"]: option = CONFIG["torrc.alias"][option] # tor appears to replace tabs with a space, for instance: # "accept\t*:563" is read back as "accept *:563" value = value.replace("\t", " ") # parse value if it's a size or time, expanding the units value, valueType = _parseConfValue(value) # issues GETCONF to get the values tor's currently configured to use torValues = conn.getOption(option, [], True) # multiline entries can be comma separated values (for both tor and conf) valueList = [value] if option in getMultilineParameters(): valueList = [val.strip() for val in value.split(",")] fetchedValues, torValues = torValues, [] for fetchedValue in fetchedValues: for fetchedEntry in fetchedValue.split(","): fetchedEntry = fetchedEntry.strip() if not fetchedEntry in torValues: torValues.append(fetchedEntry) for val in valueList: # checks if both the argument and tor's value are empty isBlankMatch = not val and not torValues if not isBlankMatch and not val in torValues: # converts corrections to reader friedly size values displayValues = torValues if valueType == ValueType.SIZE: displayValues = [ str_tools.get_size_label(int(val)) for val in torValues ] elif valueType == ValueType.TIME: displayValues = [ str_tools.get_time_label(int(val)) for val in torValues ] issuesFound.append((lineNumber, ValidationError.MISMATCH, ", ".join(displayValues))) # checks if any custom options are missing from the torrc for option in customOptions: # In new versions the 'DirReqStatistics' option is true by default and # disabled on startup if geoip lookups are unavailable. If this option is # missing then that's most likely the reason. # # https://trac.torproject.org/projects/tor/ticket/4237 if option == "DirReqStatistics": continue if not option in seenOptions: issuesFound.append((None, ValidationError.MISSING, option)) return issuesFound
def _getAvgLabel(self, isPrimary): total = self.primaryTotal if isPrimary else self.secondaryTotal total += self.prepopulatePrimaryTotal if isPrimary else self.prepopulateSecondaryTotal return "avg: %s/sec" % str_tools.get_size_label( (total / max(1, self.tick + self.prepopulateTicks)) * 1024, 1, False, CONFIG["features.graph.bw.transferInBytes"])
def validate(contents = None): """ Performs validation on the given torrc contents, providing back a listing of (line number, issue, msg) tuples for issues found. If the issue occures on a multiline torrc entry then the line number is for the last line of the entry. Arguments: contents - torrc contents """ conn = torTools.getConn() customOptions = getCustomOptions() issuesFound, seenOptions = [], [] # Strips comments and collapses multiline multi-line entries, for more # information see: # https://trac.torproject.org/projects/tor/ticket/1929 strippedContents, multilineBuffer = [], "" for line in _stripComments(contents): if not line: strippedContents.append("") else: line = multilineBuffer + line multilineBuffer = "" if line.endswith("\\"): multilineBuffer = line[:-1] strippedContents.append("") else: strippedContents.append(line.strip()) for lineNumber in range(len(strippedContents) - 1, -1, -1): lineText = strippedContents[lineNumber] if not lineText: continue lineComp = lineText.split(None, 1) if len(lineComp) == 2: option, value = lineComp else: option, value = lineText, "" # Tor is case insensetive when parsing its torrc. This poses a bit of an # issue for us because we want all of our checks to be case insensetive # too but also want messages to match the normal camel-case conventions. # # Using the customOptions to account for this. It contains the tor reported # options (camel case) and is either a matching set or the following defaut # value check will fail. Hence using that hash to correct the case. # # TODO: when refactoring for stem make this less confusing... for customOpt in customOptions: if customOpt.lower() == option.lower(): option = customOpt break # if an aliased option then use its real name if option in CONFIG["torrc.alias"]: option = CONFIG["torrc.alias"][option] # most parameters are overwritten if defined multiple times if option in seenOptions and not option in getMultilineParameters(): issuesFound.append((lineNumber, ValidationError.DUPLICATE, option)) continue else: seenOptions.append(option) # checks if the value isn't necessary due to matching the defaults if not option in customOptions: issuesFound.append((lineNumber, ValidationError.IS_DEFAULT, option)) # replace aliases with their recognized representation if option in CONFIG["torrc.alias"]: option = CONFIG["torrc.alias"][option] # tor appears to replace tabs with a space, for instance: # "accept\t*:563" is read back as "accept *:563" value = value.replace("\t", " ") # parse value if it's a size or time, expanding the units value, valueType = _parseConfValue(value) # issues GETCONF to get the values tor's currently configured to use torValues = conn.getOption(option, [], True) # multiline entries can be comma separated values (for both tor and conf) valueList = [value] if option in getMultilineParameters(): valueList = [val.strip() for val in value.split(",")] fetchedValues, torValues = torValues, [] for fetchedValue in fetchedValues: for fetchedEntry in fetchedValue.split(","): fetchedEntry = fetchedEntry.strip() if not fetchedEntry in torValues: torValues.append(fetchedEntry) for val in valueList: # checks if both the argument and tor's value are empty isBlankMatch = not val and not torValues if not isBlankMatch and not val in torValues: # converts corrections to reader friedly size values displayValues = torValues if valueType == ValueType.SIZE: displayValues = [str_tools.get_size_label(int(val)) for val in torValues] elif valueType == ValueType.TIME: displayValues = [str_tools.get_time_label(int(val)) for val in torValues] issuesFound.append((lineNumber, ValidationError.MISMATCH, ", ".join(displayValues))) # checks if any custom options are missing from the torrc for option in customOptions: # In new versions the 'DirReqStatistics' option is true by default and # disabled on startup if geoip lookups are unavailable. If this option is # missing then that's most likely the reason. # # https://trac.torproject.org/projects/tor/ticket/4237 if option == "DirReqStatistics": continue if not option in seenOptions: issuesFound.append((None, ValidationError.MISSING, option)) return issuesFound
# Accessing controller with Controller.from_port(port = 9151) as controller: controller.authenticate() # Accessing circuits in controller for circ in sorted(controller.get_circuits()): # Filtering only 3 relay complete circuits if circ.status != CircStatus.BUILT or len(circ.path) < 3: continue print print "Circuit %s (%s)" % (circ.id, circ.purpose) cirStr = "" # Accessing relays in each circuit for i, entry in enumerate(circ.path): if i == 0: str = "Entry node" elif i == len(circ.path) - 1: str = "Exit node" else: str = "Middle node" fingerprint, nickname = entry bw_rate = get_size_label(int(controller.get_conf('BandwidthRate', '0'))) desc = controller.get_network_status(fingerprint, None) address = desc.address if desc else 'unknown' countrycode = controller.get_info("ip-to-country/%s" % desc.address, "unknown") country = gi.country_name_by_addr(address) cirStr = cirStr + " %s: [IP: %s, Country: %s, Bandwidth: %s/s] ->" % (str, address, country, bw_rate) print cirStr[:-2]
def draw(self, width, height): self.valsLock.acquire() isWide = width + 1 >= MIN_DUAL_COL_WIDTH # space available for content if isWide: leftWidth = max(width / 2, 77) rightWidth = width - leftWidth else: leftWidth = rightWidth = width # Line 1 / Line 1 Left (system and tor version information) sysNameLabel = "arm - %s" % self.vals["sys/hostname"] contentSpace = min(leftWidth, 40) if len(sysNameLabel) + 10 <= contentSpace: sysTypeLabel = "%s %s" % (self.vals["sys/os"], self.vals["sys/version"]) sysTypeLabel = uiTools.cropStr( sysTypeLabel, contentSpace - len(sysNameLabel) - 3, 4) self.addstr(0, 0, "%s (%s)" % (sysNameLabel, sysTypeLabel)) else: self.addstr(0, 0, uiTools.cropStr(sysNameLabel, contentSpace)) contentSpace = leftWidth - 43 if 7 + len(self.vals["tor/version"]) + len( self.vals["tor/versionStatus"]) <= contentSpace: if self.vals["tor/version"] != "Unknown": versionColor = VERSION_STATUS_COLORS[self.vals["tor/versionStatus"]] if \ self.vals["tor/versionStatus"] in VERSION_STATUS_COLORS else "white" labelPrefix = "Tor %s (" % self.vals["tor/version"] self.addstr(0, 43, labelPrefix) self.addstr(0, 43 + len(labelPrefix), self.vals["tor/versionStatus"], uiTools.getColor(versionColor)) self.addstr( 0, 43 + len(labelPrefix) + len(self.vals["tor/versionStatus"]), ")") elif 11 <= contentSpace: self.addstr( 0, 43, uiTools.cropStr("Tor %s" % self.vals["tor/version"], contentSpace, 4)) # Line 2 / Line 2 Left (tor ip/port information) x, includeControlPort = 0, True if self.vals["tor/orPort"]: myAddress = "Unknown" if self.vals["tor/orListenAddr"]: myAddress = self.vals["tor/orListenAddr"] elif self.vals["tor/address"]: myAddress = self.vals["tor/address"] # acting as a relay (we can assume certain parameters are set dirPortLabel = ", Dir Port: %s" % self.vals[ "tor/dirPort"] if self.vals["tor/dirPort"] != "0" else "" for label in (self.vals["tor/nickname"], " - " + myAddress, ":" + self.vals["tor/orPort"], dirPortLabel): if x + len(label) <= leftWidth: self.addstr(1, x, label) x += len(label) else: break else: # non-relay (client only) if self._isTorConnected: self.addstr(1, x, "Relaying Disabled", uiTools.getColor("cyan")) x += 17 else: statusTime = torTools.getConn().getHeartbeat() if statusTime: statusTimeLabel = time.strftime("%H:%M %m/%d/%Y, ", time.localtime(statusTime)) else: statusTimeLabel = "" # never connected to tor self.addstr(1, x, "Tor Disconnected", curses.A_BOLD | uiTools.getColor("red")) self.addstr(1, x + 16, " (%spress r to reconnect)" % statusTimeLabel) x += 39 + len(statusTimeLabel) includeControlPort = False if includeControlPort: if self.vals["tor/controlPort"] == "0": # connected via a control socket self.addstr( 1, x, ", Control Socket: %s" % self.vals["tor/socketPath"]) else: if self.vals["tor/isAuthPassword"]: authType = "password" elif self.vals["tor/isAuthCookie"]: authType = "cookie" else: authType = "open" if x + 19 + len(self.vals["tor/controlPort"]) + len( authType) <= leftWidth: authColor = "red" if authType == "open" else "green" self.addstr(1, x, ", Control Port (") self.addstr(1, x + 16, authType, uiTools.getColor(authColor)) self.addstr(1, x + 16 + len(authType), "): %s" % self.vals["tor/controlPort"]) elif x + 16 + len(self.vals["tor/controlPort"]) <= leftWidth: self.addstr( 1, 0, ", Control Port: %s" % self.vals["tor/controlPort"]) # Line 3 / Line 1 Right (system usage info) y, x = (0, leftWidth) if isWide else (2, 0) if self.vals["stat/rss"] != "0": memoryLabel = str_tools.get_size_label(int(self.vals["stat/rss"])) else: memoryLabel = "0" uptimeLabel = "" if self.vals["tor/startTime"]: if self.isPaused() or not self._isTorConnected: # freeze the uptime when paused or the tor process is stopped uptimeLabel = str_tools.get_short_time_label( self.getPauseTime() - self.vals["tor/startTime"]) else: uptimeLabel = str_tools.get_short_time_label( time.time() - self.vals["tor/startTime"]) sysFields = ((0, "cpu: %s%% tor, %s%% arm" % (self.vals["stat/%torCpu"], self.vals["stat/%armCpu"])), (27, "mem: %s (%s%%)" % (memoryLabel, self.vals["stat/%mem"])), (47, "pid: %s" % (self.vals["tor/pid"] if self._isTorConnected else "")), (59, "uptime: %s" % uptimeLabel)) for (start, label) in sysFields: if start + len(label) <= rightWidth: self.addstr(y, x + start, label) else: break if self.vals["tor/orPort"]: # Line 4 / Line 2 Right (fingerprint, and possibly file descriptor usage) y, x = (1, leftWidth) if isWide else (3, 0) fingerprintLabel = uiTools.cropStr( "fingerprint: %s" % self.vals["tor/fingerprint"], width) self.addstr(y, x, fingerprintLabel) # if there's room and we're able to retrieve both the file descriptor # usage and limit then it might be presented if width - x - 59 >= 20 and self.vals["tor/fdUsed"] and self.vals[ "tor/fdLimit"]: # display file descriptor usage if we're either configured to do so or # running out fdPercent = 100 * self.vals["tor/fdUsed"] / self.vals[ "tor/fdLimit"] if fdPercent >= 60 or CONFIG["features.showFdUsage"]: fdPercentLabel, fdPercentFormat = "%i%%" % fdPercent, curses.A_NORMAL if fdPercent >= 95: fdPercentFormat = curses.A_BOLD | uiTools.getColor( "red") elif fdPercent >= 90: fdPercentFormat = uiTools.getColor("red") elif fdPercent >= 60: fdPercentFormat = uiTools.getColor("yellow") estimateChar = "?" if self.vals[ "tor/isFdLimitEstimate"] else "" baseLabel = "file desc: %i / %i%s (" % ( self.vals["tor/fdUsed"], self.vals["tor/fdLimit"], estimateChar) self.addstr(y, x + 59, baseLabel) self.addstr(y, x + 59 + len(baseLabel), fdPercentLabel, fdPercentFormat) self.addstr(y, x + 59 + len(baseLabel) + len(fdPercentLabel), ")") # Line 5 / Line 3 Left (flags) if self._isTorConnected: y, x = (2 if isWide else 4, 0) self.addstr(y, x, "flags: ") x += 7 if len(self.vals["tor/flags"]) > 0: for i in range(len(self.vals["tor/flags"])): flag = self.vals["tor/flags"][i] flagColor = FLAG_COLORS[ flag] if flag in FLAG_COLORS.keys() else "white" self.addstr( y, x, flag, curses.A_BOLD | uiTools.getColor(flagColor)) x += len(flag) if i < len(self.vals["tor/flags"]) - 1: self.addstr(y, x, ", ") x += 2 else: self.addstr(y, x, "none", curses.A_BOLD | uiTools.getColor("cyan")) else: y = 2 if isWide else 4 statusTime = torTools.getConn().getHeartbeat() statusTimeLabel = time.strftime("%H:%M %m/%d/%Y", time.localtime(statusTime)) self.addstr(y, 0, "Tor Disconnected", curses.A_BOLD | uiTools.getColor("red")) self.addstr(y, 16, " (%s) - press r to reconnect" % statusTimeLabel) # Undisplayed / Line 3 Right (exit policy) if isWide: exitPolicy = self.vals["tor/exitPolicy"] # adds note when default exit policy is appended if exitPolicy == "": exitPolicy = "<default>" elif not exitPolicy.endswith((" *:*", " *")): exitPolicy += ", <default>" self.addstr(2, leftWidth, "exit policy: ") x = leftWidth + 13 # color codes accepts to be green, rejects to be red, and default marker to be cyan isSimple = len(exitPolicy) > rightWidth - 13 policies = exitPolicy.split(", ") for i in range(len(policies)): policy = policies[i].strip() policyLabel = policy.replace("accept", "").replace( "reject", "").strip() if isSimple else policy policyColor = "white" if policy.startswith("accept"): policyColor = "green" elif policy.startswith("reject"): policyColor = "red" elif policy.startswith("<default>"): policyColor = "cyan" self.addstr(2, x, policyLabel, curses.A_BOLD | uiTools.getColor(policyColor)) x += len(policyLabel) if i < len(policies) - 1: self.addstr(2, x, ", ") x += 2 else: # (Client only) Undisplayed / Line 2 Right (new identity option) if isWide: conn = torTools.getConn() newnymWait = conn.getNewnymWait() msg = "press 'n' for a new identity" if newnymWait > 0: pluralLabel = "s" if newnymWait > 1 else "" msg = "building circuits, available again in %i second%s" % ( newnymWait, pluralLabel) self.addstr(1, leftWidth, msg) self.valsLock.release()