[or-cvs] r20674: {arm} Few issues discussed on irc. added: changelog and cleaned up (in arm/trunk: . interface)

atagar at seul.org atagar at seul.org
Sun Sep 27 08:50:41 UTC 2009


Author: atagar
Date: 2009-09-27 04:50:41 -0400 (Sun, 27 Sep 2009)
New Revision: 20674

Added:
   arm/trunk/ChangeLog
   arm/trunk/README
   arm/trunk/TODO
Removed:
   arm/trunk/readme.txt
Modified:
   arm/trunk/arm.py
   arm/trunk/interface/bandwidthMonitor.py
   arm/trunk/interface/connPanel.py
   arm/trunk/interface/controller.py
   arm/trunk/interface/cpuMemMonitor.py
   arm/trunk/interface/headerPanel.py
   arm/trunk/interface/hostnameResolver.py
Log:
Few issues discussed on irc.
added: changelog and cleaned up todo documents (requested by arma)
added: option in controller.py to disable connection panel (feature request by Sebastian)
fix: failed to work on osx and bsd due to crashes after failed system calls (caught by Sebastian and Christopher Davis)
fix: reloading static data in bandwidth panel after HUP (caught by hexa)
fix: couple alignment issues with the connection listings



Added: arm/trunk/ChangeLog
===================================================================
--- arm/trunk/ChangeLog	                        (rev 0)
+++ arm/trunk/ChangeLog	2009-09-27 08:50:41 UTC (rev 20674)
@@ -0,0 +1,226 @@
+CHANGE LOG
+
+9/27/09 - version 1.1.2
+Few issues discussed on irc.
+
+    * added: changelog and cleaned up todo documents (requested by arma)
+    * added: option in controller.py to disable connection panel (feature request by Sebastian)
+    * fix: failed to work on osx and bsd due to crashes after failed system calls (caught by Sebastian and Christopher Davis)
+    * fix: reloading static data in bandwidth panel after HUP (caught by hexa)
+    * fix: couple alignment issues with the connection listings
+
+9/23/09 - version 1.1.1 (r20655)
+Bundle of semi-low hanging fruit, including a few issues discussed on irc.
+
+    * added: showing extra parameters in connection listings if room's available
+    * added: identifying directory server connections
+    * change: providing an error message if running an incompatible python version (issue spotted by arma)
+    * change: giving arm a version to help in bug reports
+    * change: minor tweak to the wording of a faq entry (requested by Sebastian)
+    * fix: wasn't accounting for RelayBandwidthRate/Burst in effective bandwidth (caught by hexa and arma)
+    * fix: timing issue when shutting down (caught by arma)
+    * fix: couple issues with connection time being tracked when paused
+    * fix: preserving old results when netstat fails
+
+9/6/09 - r20493
+Several substantial features (last tasks for arm's todo list).
+
+    * added: scroll bars for connections listing and event log
+    * added: made log scrollable (feature request by StrangeCharm)
+    * added: regular expression filtering for log (feature request by StrangeCharm)
+    * added: connection uptimes (time since connection was first made)
+    * added: identifying client from server connections and providing popup for client circuits
+    * added: graph for system resource usage (cpu/memory)
+    * change: removed cursor toggling option for connection page
+    * fix: minor display issue when changing event types
+
+8/22/09 - r20354
+Several fixes and changes, mostly concerning the graph panel and making better use of screen real estate.
+
+    * added: labeled the graph's x-axis and reordered the information with changes omitted for small (tty sized) terminals (feature request by StrangeCharm)
+    * added: doubling up contents of header panel in case of wide screens to take advantage of added space
+    * added: exit policy to header if a wide display
+    * change: added precision for bandwidth measurements
+    * change: using "orconn-status" info to eliminated ambiguity in identifying inbound connection fingerprints (clever idea, but had very little impact)
+    * fix: when sighup signal is received reloads torrc and internal state (caught by StrangeCharm)
+    * fix: probable resolution of nasty concurrent bug concerning access to connection cache
+    * fix: minor issues concerning connection panel including graph widths and miscalculating local maxima
+    * fix: short circuits fingerprint cache when looking up localhost descriptor (preventing lookup failures)
+    * fix: minor issues with connection panel and description popups when no connections are available
+    * fix: descriptor popup wasn't determining if the first visible line belonged to an encryption block
+    * fix: made interface more resilient against arbitrary resizing (such as during popups)
+
+8/17/09 - r20331
+Work done over this last week.
+
+    * added: popup for raw consensus description
+    * added: total bandwidth measurement (feature request by StrangeCharm)
+    * added: connection entry for lookup of local consensus data
+    * change: widened graphs to utilize full screen width (clever idea by StrangeCharm)
+    * change: preserving runtime and pid when shutting down
+    * change: few tweaks to the readme
+    * fix: joining on worker daemon threads to exit gracefully (had a noisy race condition)
+    * fix: using BW events to keep connection count graph in sync with bandwidth graph
+    * fix: can now support graphs of multiple sizes
+
+8/8/09 - r20233
+Rewrote graph panel so it can handle any real time statistics.
+
+    * added: option to graph connection counts (feature request by phobos)
+    * added: custom graph bounds (global or local maxima)
+
+8/4/09
+Announced the project on the or-talk mailing list today which spurred an interview with Brenno Winter (who works on the cleverly named Little Sister project). The interview is available here.
+
+8/3/09 - r20210
+Added start of a faq to the readme in preparation for announcement on or-talk.
+
+7/30/09 - r20198
+Work done over the trip.
+
+    * added: customizable update interval for bandwidth graph (feature request by StrangeCharm)
+    * change: noted new project page in the readme (www.atagar.com/arm)
+    * change: added word wrapping to conf panel
+    * change: added function for custom popup menus
+    * change: logs error message when required event types are unsupported rather than throwing an exception
+    * change: using different screenshot images
+    * fix: resolved issue that caused monitor to think tor was resumed when quit
+    * fix: bug with panel utility's resize detection
+    * fix: resorts connections after NEWDESC and NEWCONSENSUS events
+    * fix: forgetting to to resume monitor at multiple points after a temporary pause
+    * fix: minor refactoring based on suggestions from pylint (unused imports and such)
+
+7/22/09 - r20115
+Another small grab bag update.
+
+    * added: version status to header panel
+    * change: noted "Common *nix commands including: ps, pidof, host, and netstat" among requirements in readme
+    * change: took some tricks from Mike's ConsensusTracker to further improve match rate
+    * fix: type mismatch that greatly diminished fingerprint matching
+    * fix: accidentally used idhash rather than idhex for fingerprints when updating cache with the contents of a NEWDESC event
+
+7/21/09 - r20100
+Quick fixes based on discussion on irc.
+
+    * change: provides warning when geoip database is unavailable (thanks to SwissTorExit and karsten)
+    * fix: missing import for the socket module
+
+7/20/09 - r20096, r20097, r20098
+Couple fixes so arm plays nicely in the case of multiple running tor instances.
+
+    * fix: can now deal with multiple tor instances: checks pid of process with the open control port
+    * fix: if only one tor process is running use that pid (netstat fails if running as a different user
+
+7/19/09 - r20087, r20090
+Last substantial feature on my to-do list.
+
+    * added: connections can be selected to view consensus details (very spiffy!)
+    * added: listing selection is by menu rather than cycling
+    * fix: couple bugs, the most interesting being when netstat can't resolve a connections listing (spotted by phobos)
+
+7/18/09 - r20078, r20079
+Miscellaneous fix and feature batch.
+
+    * added: relay's flags to the header
+    * added: listing by relay nickname
+    * added: additional event aliases and option for NEWCONSENSUS
+    * added (phobos): screenshot of arm in action so people can see what it looks like
+    * change: use constant "Listing" label for sorting rather than current view
+    * change: removed 'reload torrc' option (deceptive and useless)
+    * fix: updates cached consensus mappings with NEWDESC and NEWCONSENSUS events
+
+7/14/09 - r20016
+Resolved a few quick bugs:
+
+    * fix: added fingerprint lookup cache to resolve substantial performance issue
+    * fix: hostname resolution progress accounts for newly added entries (no more negative progress)
+    * fix: resolved bug that prevented arm from starting if too small
+    * fix: ordering issue when sorting unresolved ip addresses
+
+7/11/09 - r19975
+Connections panel can now list by IP, hostname, or fingerprint: reverse resolution was easy, but comparing three different implementations and making it non-blocking with a pausable thread-pool backend? Not so much.
+
+7/8/09 - r19953, r19957
+Just got back from Toorcamp. Preliminary connection page and miscellaneous additions.
+
+    * added: basic connection listing page (using netstat results)
+    * added: connection listing now has user configurable sort functionality (it's actually pretty spiffy: supports secondary and tertiary sub-keys)
+    * added: 'addfstr' to util which allows for embedded formatting tags (VERY helpful)
+    * added: help shows page's current settings
+    * added: made bandwidth panel toggleable
+    * added: avg bandwidth to bottom of panel
+    * fix: prevented header from being paused on page change
+    * fix: prevented bandwidth accounting events from being lost when paused
+
+6/14/09 - r19716
+Decently big batch of feature additions and bug fixes.
+
+    * added: second page that presents torrc with syntax highlighting, optional comment stripping, etc
+    * added: ps sampling (cpu/memory usage, pid, and uptime)
+    * added: help popup with page controls
+    * fix: corrected issue that caused periodic refreshing to fail
+    * fix: accounting reset time takes into account DST
+    * fix: make accounting input and header pausable
+
+6/10/09 - r19708, r19709
+Couple quick changes.
+
+    * change: removed '--path-to-torctl' startup option
+    * fix: accounting 'time to reset' now includes gmt to local conversion
+
+6/7/09 - r19646, r19655, r19656
+Couple features.
+
+    * added: svn external inclusion of TorCtl
+    * added: bandwidth panel now displays accounting data if set
+
+6/6/09 - r19636, r19637
+Quick change based on discussion on irc.
+
+    * added: command line argument to specify location of TorCtl without changing Python path first (feature request by phobos)
+
+6/5/09 - r19629
+Substantial refactoring changes.
+
+    * change: switched from a functional to an OO implementation which further simplified the controller: as an added plus this should make adding additional 'pages' trivial
+    * change: offloaded resizing to the curses wrapper
+    * fix: dealt with another curses wtf bug where panels wouldn't repaint unless done in a specific order
+
+6/4/09 - r19626
+Tested and corrected formatting for all event types except STREAM and STREAM_BW (not sure how to make those occur...).
+
+6/2/09 - r19615, r19619, r19620
+Introduced layer of abstraction from curses, simplifying its use and greatly improving reliability.
+
+    * added: introduced wrapper to hide curses ugliness which greatly simplified interface code
+    * added: notice when relay's been silent for five seconds (based on BW events so probably due to Tor being closed), another idea by karsten
+    * changed: unchecked events have stubs to present information and provide debugging information in case of type mismatch
+    * fix: all problems with resizing: it's now rock solid
+
+5/29/09 - r19580, r19594
+Fixes for several rather sinister reliability problems:
+
+    * added: allows logged events to be changed while running (suggested feature by karsten) and experimenting with a more modular design
+    * fix: added non-blocking reentrant locks to fix concurrency errors that caused chaotic terminal glitches (such as switching to a Chinese character set)
+    * fix: now fully handles resizing (including vertical)
+    * fix: using new capabilities in TorCtl including cookie authentication and disabling logging
+    * fix: bandwidth graph bug when paused
+    * fix: occasionally refreshes static content in case of graphical hiccups
+    * fix: added workaround for obscure curses caching bug that prevented portions of the screen from being redrawn
+    * fix: bug preventing initialization if too small
+
+5/25/09 - r19567
+Few small tweaks including:
+
+    * added: tiny shell script to alias starting
+    * added: more informative error message if TorCtl isn't available
+    * change: defaultly logged events
+    * change: make inclusion of 'unknown' events toggleable
+
+5/24/09 - r19548, r19549, r19550, r19551
+Initial version of arm (terminal relay status monitor). Repository set up by arma.
+
+    * fix: bug concerning undefined exit policy
+    * fix: resolved issue that prevented monitor from functioning in terminals without curs_set support
+
+

Copied: arm/trunk/README (from rev 20655, arm/trunk/readme.txt)
===================================================================
--- arm/trunk/README	                        (rev 0)
+++ arm/trunk/README	2009-09-27 08:50:41 UTC (rev 20674)
@@ -0,0 +1,84 @@
+arm (anonymizing relay monitor) - Terminal status monitor for Tor relays.
+Developed by Damian Johnson (www.atagar.com - atagar1 at gmail.com)
+All code under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
+Project page: www.atagar.com/arm
+
+Description:
+Command line application for monitoring Tor relays, providing real time status 
+information such as the current configuration, bandwidth usage, message log, 
+connections, etc. This uses a curses interface much like 'top' does for system 
+usage. The application is intended for command-line aficionados, ssh 
+connections, and anyone stuck with a tty terminal for checking their relay's 
+status. Releases should be stable so if you manage to make it crash (or have a 
+feature request) then please let me know!
+
+The project was originally proposed in 2008 by Jacob and Karsten:
+  http://archives.seul.org/or/dev/Jan-2008/msg00005.html
+
+An interview by Brenno Winter discussing the project is available at:
+  http://www.atagar.com/arm/HFM_INT_0001.mp3
+
+Requirements:
+Python 2.5
+TorCtl (retrieved in svn checkout)
+Common *nix commands including: ps, pidof, host, and netstat
+Tor is running with an available control port. This means either...
+  ... starting Tor with '--controlport <PORT>'
+  ... or including 'ControlPort <PORT>' in your torrc
+
+This is started via 'arm' (use the '--help' argument for usage).
+
+FAQ:
+> Why is it called 'arm'?
+
+Simple - because it makes the command short and memorable. Terminal 
+applications need to be easy to type (like 'top', 'ssh', etc), and anything 
+longer is just begging command-line aficionados to alias it down. I chose the 
+meaning of the acronym ('anonymizing relay monitor') afterward.
+
+> If you're listing connections then what about exit nodes? Won't this include 
+people's traffic?
+
+While arm isn't intended to be a sniffer it does provide real time connection 
+data which, for exit nodes, includes the endpoints being visited through you. 
+Unfortunately this is pretty unavoidable. The control port doesn't provide a 
+means of distinguishing those connections and trying to figure it out by 
+correlating against consensus data has proved pretty inaccurate.
+
+That said, this really isn't much of a concern. For Tor users the real threats 
+come from things like Wireshark and MITM attacks on their unencrypted traffic. 
+Simply seeing an unknown individual's endpoints is no great feat in itself. 
+Just attach netstat to a cron job and voilà! You've got a sniffer that's just 
+as mighty as arm.
+
+> Is it harmful to share the information provided by arm?
+
+Not really, but it's discouraged. The original plan for arm included a special 
+emphasis that it wouldn't log any data. The reason is that if a large number 
+of relay operators published the details of their connections then correlation 
+attacks could break Tor user's anonymity. Just show some moderation in what 
+you share and it should be fine.
+
+> Is there any chance that arm will leak data?
+
+Yes - arm is a passive listener with one exception. The second page 
+(connections) provides the hostnames of Tor relays you're connected to. This 
+means reverse DNS lookups which, if monitored, could leak your current 
+connections to an eavesdropper. However, lookups are only made upon request 
+(when showing connection details or listing connections by hostname) and you 
+can disable lookups entirely with 'r' - see the page's help for the current 
+status.
+
+That said, this is not a terribly big whoop. ISPs and anyone sniffing your 
+connection already has this data - the only difference is that instead of 
+saying "I am talking to x" you're saying "I'm talking to x, who's x?", meaning 
+the resolver's also aware of who they are.
+
+> When arm starts it gives "Unable to resolve tor pid, abandoning connection 
+listing"... why?
+
+If you're running multiple instances of tor then arm needs to figure out which 
+pid belongs to the open control port. If it's running as a different user 
+(such as being in a chroot jail) then it's probably failing due to permission 
+issues. Arm still runs, just no connection listing or ps stats.
+

Added: arm/trunk/TODO
===================================================================
--- arm/trunk/TODO	                        (rev 0)
+++ arm/trunk/TODO	2009-09-27 08:50:41 UTC (rev 20674)
@@ -0,0 +1,122 @@
+TODO
+
+- Bugs
+	* make netstat lookups a best-effort service (separate from draw thread)
+			Call appears to be heavier than expected and causing display to be
+			unusable on especially active relays (like directory servers).
+			caught by arma and StrangeCharm, notify coderman for testing
+	* Mac OSX and BSD may have issues with netstat options
+			Reported that they aren't cross platform. Possibly use lsof as a 
+			fallback if an issue's detected.
+			caught by Christopher Davis
+	* quitting can hang several seconds when there's hostnames left to resolve
+			Not sure how to address this - problem is that the calls to 'host' can 
+			take a while to time out. Might need another thread to kill the calls?
+			Or forcefully terminate thread if it's taking too long (might be noisy)?
+	* connection details covers right side
+	* version labels provided on Debian are longer than expected
+			caught by hexa
+	* unable to load torrc if it was loaded via a relative path
+			When tor's started via "tor -f <relative path>" we don't know what it's 
+			relative of - check to see if there's a way of finding the pwd of
+			another process.
+			caught by arma
+	* new connections don't have uptime tracked when not visible
+			Previous fix attempted to resolve, but evidently didn't work.
+
+- Features / Site
+	* provide observed bandwidth
+			Newer relays have a 'w' entry that states the bandwidth and old versions
+			have client side measurements (third argument in 'Bandwidth' of
+			descriptor, note that it's in KB/s). Label the former (server side) as 
+			'Measured' and later (client side) as 'Observed' to differentiate.
+			requested by arma
+	* show advertised bandwidth
+			if set and there's extra room available show 'MaxAdvertisedBandwidth'
+	* when help popup is showing options let them be directly opened
+			requested by arma
+	* update site's screenshots (pretty out of date...)
+	* add arm to listings of support programs
+			https://wiki.torproject.org/noreply/TheOnionRouter/SupportPrograms
+			https://www.torproject.org/projects/
+
+- Ideas (low priority)
+	* provide performance ARM-DEBUG events
+			Might help with debugging bottlenecks. This requires that there's more
+			refined controls for selecting logged arm runlevel.
+	* show qos stats
+			Take a look at 'linux-tor-prio.sh' to see if any of the stats are 
+			available and interesting.
+	* get a test environment for Mac OSX or BSD
+			Set up a vm for FreeBSD but found working in it to be... painful (wasted
+			five hours and gave up when even asking for a working copy of vim was 
+			too much to ask). As for OSX seems that getting a test environment would
+			cost quite a bit. Hence mothballing this - someone that actually uses
+			these platforms will need to resolve portability issues if they arise.
+	* localization
+			Abstract strings from code and provide on translation portal. Thus far
+			there hasn't been any requests for this.
+	* provide option for a consensus page
+			Shows full consensus with an interface similar to the connection panel.
+			For this Mike's ConsensusTracker would be helpful (though boost the
+			startup time by several seconds)
+	* provide Debian repository for arm
+			Look into debian packaging, note system call dependencies, and mail
+			submit at bugs.debian.org with subject "RFP: arm" and starting with a line
+			"Package: wnpp".
+			requested by helmut
+
+- Control Protocol Wishlist (low priority)
+	* listing of tor's current connections (netstat / lsof replacement)
+			Keeping the netstat available would be good for auditing (external view
+			of tor and more likely monitored by host based IDS) but tor's listing
+			would probably be more effecient, accurate, and could contain additional
+			details making it a preferable default.
+	* bandwidth usage per connection
+			This would need to be rounded and averaged over time to avoid 
+			correlation problems. Probably the most interesting stat arm currently
+			doesn't have since for most purposes (like security threats) especially
+			active connections are of most interest.
+	* identification of hop type
+			Identification if the first, middle or last hop. When this is available
+			I'll hide exit connections by default. Another interesting distinction
+			would be when we're serving directory data verses acting as a relay.
+	* associate connections to circuits
+			Currently listing is connection based rather than circuit, ie it lists:
+			previous hop -> localhost
+			previous hop -> localhost
+			localhost -> next hop
+			
+			rather than:
+			previous hop -> localhost -> next hop
+			previous hop -> localhost -> *unestablished*
+			
+			From a debugging and secuirty standpoint this could highlight potential
+			issues, for instance relays really shouldn't have any non-client
+			connections like:
+			*unestablished* -> localhost -> next hop
+			
+			and entries like:
+			previous hop -> localhost -> *extension failed (error X)*
+			
+			might indicate a firewall blocking tor outbound connections. This would
+			be especially helpful if paired with server related circuit status
+			events (which would note attempted extensions, failures, etc). We could
+			also note other circuit based stats like the amount of buffered data.
+	* mapping of ip/port to fingerprint
+			Currently inferring the mappings but this only has around a 90% success
+			rate (not sure why it fails...). Tor has an internal connection
+			identifier so what would probably be best is bidirectional translation
+			functions with that, ie getting fingerprint would be done via:
+			ip/port -> connection id -> fingerprint
+			
+			In theory this should be able to tell us if the connection is the first
+			or last hop (since in those cases the foreign address doesn't have a
+			fingerprint).
+	* additional get_info data
+			effective relay bandwidth / burst - currently internally mimicing the
+				logic of tor (which is RelayBandwidthRate/Burst if set, otherwise 
+				BandwidthRate/Burst)
+			list of directory authorities recognized by that instance of tor
+			total data relayed by tor - this is already kinda tracked for accounting
+

Modified: arm/trunk/arm.py
===================================================================
--- arm/trunk/arm.py	2009-09-27 07:58:53 UTC (rev 20673)
+++ arm/trunk/arm.py	2009-09-27 08:50:41 UTC (rev 20674)
@@ -19,8 +19,8 @@
 from interface import controller
 from interface import logPanel
 
-VERSION = "1.1.1"
-LAST_MODIFIED = "Sep 23, 2009"
+VERSION = "1.1.2"
+LAST_MODIFIED = "Sep 27, 2009"
 
 DEFAULT_CONTROL_ADDR = "127.0.0.1"
 DEFAULT_CONTROL_PORT = 9051

Modified: arm/trunk/interface/bandwidthMonitor.py
===================================================================
--- arm/trunk/interface/bandwidthMonitor.py	2009-09-27 07:58:53 UTC (rev 20673)
+++ arm/trunk/interface/bandwidthMonitor.py	2009-09-27 08:50:41 UTC (rev 20674)
@@ -27,18 +27,29 @@
     self.conn = conn              # Tor control port connection
     self.accountingInfo = None    # accounting data (set by _updateAccountingInfo method)
     
-    if conn:
-      self.isAccounting = conn.get_info('accounting/enabled')['accounting/enabled'] == '1'
+    # dummy values for static data
+    self.isAccounting = False
+    self.bwRate, self.bwBurst = -1, -1
+    self.resetStaticData()
+  
+  def resetStaticData(self):
+    """
+    Checks with tor for static bandwidth parameters (rates, accounting
+    information, etc).
+    """
+    
+    try:
+      if not self.conn: raise ValueError
+      self.isAccounting = self.conn.get_info('accounting/enabled')['accounting/enabled'] == '1'
       
       # static limit stats for label, uses relay stats if defined (internal behavior of tor)
-      bwStats = conn.get_option(['BandwidthRate', 'BandwidthBurst'])
-      relayStats = conn.get_option(['RelayBandwidthRate', 'RelayBandwidthBurst'])
+      bwStats = self.conn.get_option(['BandwidthRate', 'BandwidthBurst'])
+      relayStats = self.conn.get_option(['RelayBandwidthRate', 'RelayBandwidthBurst'])
       
       self.bwRate = util.getSizeLabel(int(bwStats[0][1] if relayStats[0][1] == "0" else relayStats[0][1]))
       self.bwBurst = util.getSizeLabel(int(bwStats[1][1] if relayStats[1][1] == "0" else relayStats[1][1]))
-    else:
-      self.isAccounting = False
-      self.bwRate, self.bwBurst = -1, -1
+    except (ValueError, TorCtl.TorCtlClosed):
+      pass # keep old values
     
     # this doesn't track accounting stats when paused so doesn't need a custom pauseBuffer
     contentHeight = 13 if self.isAccounting else 10

Modified: arm/trunk/interface/connPanel.py
===================================================================
--- arm/trunk/interface/connPanel.py	2009-09-27 07:58:53 UTC (rev 20673)
+++ arm/trunk/interface/connPanel.py	2009-09-27 08:50:41 UTC (rev 20674)
@@ -122,6 +122,7 @@
     self.orconnStatusCacheValid = False   # indicates if cache has been invalidated
     self.clientConnectionCache = None     # listing of nicknames for our client connections
     self.clientConnectionLock = RLock()   # lock for clientConnectionCache
+    self.isDisabled = False               # prevent panel from updating entirely
     
     self.isCursorEnabled = True
     self.cursorSelection = None
@@ -205,7 +206,7 @@
     Reloads netstat results.
     """
     
-    if not self.pid: return
+    if not self.pid or self.isDisabled: return
     self.connectionsLock.acquire()
     self.clientConnectionLock.acquire()
     
@@ -432,7 +433,7 @@
                 src = "localhost:%-5s" % entry[CONN_L_PORT]
                 
                 # space available for foreign hostname (stretched to claim any free space)
-                foreignHostnameSpace = self.maxX - len(self.nickname) - 38
+                foreignHostnameSpace = self.maxX - 42 - xOffset
                 
                 etc = ""
                 if self.maxX > 102 + xOffset:
@@ -448,7 +449,7 @@
                 if self.maxX > 151 + xOffset:
                   # show nickname (column width: min 17 characters, uses half of the remainder)
                   nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
-                  nicknameSpace = 15 + (self.maxX - 151) / 2
+                  nicknameSpace = 15 + (self.maxX - xOffset - 151) / 2
                   foreignHostnameSpace -= (nicknameSpace + 2)
                   
                   if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
@@ -488,7 +489,7 @@
                 else: dst = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
                 
                 # space available for foreign nickname
-                foreignNicknameSpace = self.maxX - len(self.nickname) - 27
+                foreignNicknameSpace = self.maxX - len(self.nickname) - 27 - xOffset
                 
                 etc = ""
                 if self.maxX > 92 + xOffset:

Modified: arm/trunk/interface/controller.py
===================================================================
--- arm/trunk/interface/controller.py	2009-09-27 07:58:53 UTC (rev 20673)
+++ arm/trunk/interface/controller.py	2009-09-27 08:50:41 UTC (rev 20674)
@@ -25,6 +25,7 @@
 import cpuMemMonitor
 import connCountMonitor
 
+DISABLE_CONNECTIONS_PAGE = False
 REFRESH_RATE = 5        # seconds between redrawing screen
 cursesLock = RLock()    # global curses lock (curses isn't thread safe and
                         # concurrency bugs produce especially sinister glitches)
@@ -89,7 +90,14 @@
             msgText = "Resolving hostnames (%i / %i, %i%%) - press esc %sto cancel" % (entryCount, batchSize, progress, additive)
         
         if self.resolvingCounter == -1:
-          msgText = "page %i / %i - q: quit, p: pause, h: page help" % (self.page, len(PAGES))
+          currentPage = self.page
+          pageCount = len(PAGES)
+          
+          if DISABLE_CONNECTIONS_PAGE:
+            if currentPage >= 2: currentPage -= 1
+            pageCount -= 1
+          
+          msgText = "page %i / %i - q: quit, p: pause, h: page help" % (currentPage, pageCount)
       elif msgText == CTL_PAUSED:
         msgText = "Paused"
         msgAttr = curses.A_STANDOUT
@@ -233,14 +241,16 @@
   # gets pid of tor instance with control port open
   torPid = None       # None if couldn't be resolved (provides error later)
   
-  pidOfCall = os.popen("pidof tor")
-  netstatCall = None
+  pidOfCall = os.popen("pidof tor 2> /dev/null")
   try:
     # gets pid if there's only one possability
     results = pidOfCall.readlines()
-    
     if len(results) == 1 and len(results[0].split()) == 1: torPid = results[0].strip()
-    else:
+  except IOError: pass # pid call failed
+  pidOfCall.close()
+  
+  if not torPid:
+    try:
       # uses netstat to identify process with open control port (might not
       # work if tor's being run as a different user due to permissions)
       netstatCall = os.popen("netstat -npl 2> /dev/null | grep 127.0.0.1:%s" % conn.get_option("ControlPort")[0][1])
@@ -249,11 +259,9 @@
       if len(results) == 1:
         results = results[0].split()[6] # process field (ex. "7184/tor")
         torPid = results[:results.find("/")]
-  except IOError: pass # netstat call failed
+    except IOError: pass # netstat call failed
+    netstatCall.close()
   
-  pidOfCall.close()
-  if netstatCall: netstatCall.close()
-  
   panels = {
     "header": headerPanel.HeaderPanel(cursesLock, conn, torPid),
     "popup": util.Panel(cursesLock, 9),
@@ -263,6 +271,9 @@
   panels["conn"] = connPanel.ConnPanel(cursesLock, conn, torPid, panels["log"])
   panels["control"] = ControlPanel(cursesLock, panels["conn"].resolver)
   
+  # prevents netstat calls by connPanel if not being used
+  if DISABLE_CONNECTIONS_PAGE: panels["conn"].isDisabled = True
+  
   # provides error if pid coulnd't be determined (hopefully shouldn't happen...)
   if not torPid: panels["log"].monitor_event("WARN", "Unable to resolve tor pid, abandoning connection listing")
   
@@ -303,6 +314,12 @@
       # if sighup received then reload related information
       if sighupTracker.isReset:
         panels["header"]._updateParams(True)
+        panels["graph"].stats["bandwidth"].resetStaticData()
+        
+        # if bandwidth graph is being shown then height might have changed
+        if panels["graph"].currentDisplay == "bandwidth":
+          panels["graph"].height = panels["graph"].stats["bandwidth"].height
+        
         panels["torrc"].reset()
         sighupTracker.isReset = False
       
@@ -364,6 +381,11 @@
       if key == curses.KEY_LEFT: page = (page - 1) % len(PAGES)
       else: page = (page + 1) % len(PAGES)
       
+      # skip connections listing if it's disabled
+      if page == 1 and DISABLE_CONNECTIONS_PAGE:
+        if key == curses.KEY_LEFT: page = (page - 1) % len(PAGES)
+        else: page = (page + 1) % len(PAGES)
+      
       # pauses panels that aren't visible to prevent events from accumilating
       # (otherwise they'll wait on the curses lock which might get demanding)
       setPauseState(panels, isPaused, page)

Modified: arm/trunk/interface/cpuMemMonitor.py
===================================================================
--- arm/trunk/interface/cpuMemMonitor.py	2009-09-27 07:58:53 UTC (rev 20673)
+++ arm/trunk/interface/cpuMemMonitor.py	2009-09-27 08:50:41 UTC (rev 20674)
@@ -30,7 +30,7 @@
     else:
       # cached results stale - requery ps
       inbound, outbound, control = 0, 0, 0
-      psCall = os.popen('ps -p %s -o %s' % (self.headerPanel.vals["pid"], "%cpu,rss"))
+      psCall = os.popen('ps -p %s -o %s  2> /dev/null' % (self.headerPanel.vals["pid"], "%cpu,rss"))
       try:
         sampling = psCall.read().strip().split()[2:]
         psCall.close()
@@ -41,8 +41,9 @@
         else:
           self._processEvent(float(sampling[0]), float(sampling[1]) / 1024.0)
       except IOError:
-        # ps call failed
-        self.connectionPanel.monitor_event("WARN", "Unable to query ps for resource usage")
+        # ps call failed - we need to register something (otherwise timescale
+        # would be thrown off) so keep old results
+        self._processEvent(self.lastPrimary, self.lastSecondary)
   
   def getTitle(self, width):
     return "System Resources:"

Modified: arm/trunk/interface/headerPanel.py
===================================================================
--- arm/trunk/interface/headerPanel.py	2009-09-27 07:58:53 UTC (rev 20673)
+++ arm/trunk/interface/headerPanel.py	2009-09-27 08:50:41 UTC (rev 20674)
@@ -58,79 +58,83 @@
   
   def redraw(self):
     if self.win:
-      if not self.isPaused: self._updateParams()
-      
-      # extra erase/refresh is needed to avoid internal caching screwing up and
-      # refusing to redisplay content in the case of graphical glitches - probably
-      # an obscure curses bug...
-      self.win.erase()
-      self.win.refresh()
-      
-      self.clear()
-      
-      # Line 1
-      self.addstr(0, 0, "arm - %s (%s %s)" % (self.vals["sys-name"], self.vals["sys-os"], self.vals["sys-version"]))
-      
-      versionStatus = self.vals["status/version/current"]
-      versionColor = VERSION_STATUS_COLORS[versionStatus] if versionStatus in VERSION_STATUS_COLORS else "white"
-      self.addfstr(0, 43, "Tor %s (<%s>%s</%s>)" % (self.vals["version"], versionColor, versionStatus, versionColor))
-      
-      # Line 2 (authentication label red if open, green if credentials required)
-      dirPortLabel = "Dir Port: %s, " % self.vals["DirPort"] if self.vals["DirPort"] != "0" else ""
-      
-      if self.vals["IsPasswordAuthSet"]: controlPortAuthLabel = "password"
-      elif self.vals["IsCookieAuthSet"]: controlPortAuthLabel = "cookie"
-      else: controlPortAuthLabel = "open"
-      controlPortAuthColor = "red" if controlPortAuthLabel == "open" else "green"
-      
-      labelStart = "%s - %s:%s, %sControl Port (" % (self.vals["Nickname"], self.vals["address"], self.vals["ORPort"], dirPortLabel)
-      self.addfstr(1, 0, "%s<%s>%s</%s>): %s" % (labelStart, controlPortAuthColor, controlPortAuthLabel, controlPortAuthColor, self.vals["ControlPort"]))
-      
-      # Line 3 (system usage info) - line 1 right if wide
-      y, x = 0 if self.isWide else 2, 75 if self.isWide else 0
-      self.addstr(y, x, "cpu: %s%%" % self.vals["%cpu"])
-      self.addstr(y, x + 13, "mem: %s (%s%%)" % (util.getSizeLabel(int(self.vals["rss"]) * 1024), self.vals["%mem"]))
-      self.addstr(y, x + 34, "pid: %s" % (self.vals["pid"] if self.vals["etime"] else ""))
-      self.addstr(y, x + 47, "uptime: %s" % self.vals["etime"])
-      
-      # Line 4 (fingerprint) - line 2 right if wide
-      y, x = 1 if self.isWide else 3, 75 if self.isWide else 0
-      self.addstr(y, x, "fingerprint: %s" % self.vals["fingerprint"])
-      
-      # Line 5 (flags) - line 3 left if wide
-      flagLine = "flags: "
-      for flag in self.vals["flags"]:
-        flagColor = FLAG_COLORS[flag] if flag in FLAG_COLORS.keys() else "white"
-        flagLine += "<b><%s>%s</%s></b>, " % (flagColor, flag, flagColor)
-      
-      if len(self.vals["flags"]) > 0: flagLine = flagLine[:-2]
-      self.addfstr(2 if self.isWide else 4, 0, flagLine)
-      
-      # Line 3 right (exit policy) - not present if not wide
-      if self.isWide:
-        exitPolicy = self.vals["ExitPolicy"]
+      if not self.lock.acquire(False): return
+      try:
+        if not self.isPaused: self._updateParams()
         
-        # adds note when default exit policy is appended
-        if exitPolicy == None: exitPolicy = "<default>"
-        elif not exitPolicy.endswith("accept *:*") and not exitPolicy.endswith("reject *:*"):
-          exitPolicy += ", <default>"
+        # extra erase/refresh is needed to avoid internal caching screwing up and
+        # refusing to redisplay content in the case of graphical glitches - probably
+        # an obscure curses bug...
+        self.win.erase()
+        self.win.refresh()
         
-        policies = exitPolicy.split(", ")
+        self.clear()
         
-        # color codes accepts to be green, rejects to be red, and default marker to be cyan
-        isSimple = len(policies) <= 2 # if policy is short then it's kept verbose, otherwise 'accept' and 'reject' keywords removed
-        for i in range(len(policies)):
-          policy = policies[i].strip()
-          displayedPolicy = policy if isSimple else policy.replace("accept", "").replace("reject", "").strip()
-          if policy.startswith("accept"): policy = "<green><b>%s</b></green>" % displayedPolicy
-          elif policy.startswith("reject"): policy = "<red><b>%s</b></red>" % displayedPolicy
-          elif policy.startswith("<default>"): policy = "<cyan><b>%s</b></cyan>" % displayedPolicy
-          policies[i] = policy
-        exitPolicy = ", ".join(policies)
+        # Line 1
+        self.addstr(0, 0, "arm - %s (%s %s)" % (self.vals["sys-name"], self.vals["sys-os"], self.vals["sys-version"]))
         
-        self.addfstr(2, 75, "exit policy: %s" % exitPolicy)
-      
-      self.refresh()
+        versionStatus = self.vals["status/version/current"]
+        versionColor = VERSION_STATUS_COLORS[versionStatus] if versionStatus in VERSION_STATUS_COLORS else "white"
+        self.addfstr(0, 43, "Tor %s (<%s>%s</%s>)" % (self.vals["version"], versionColor, versionStatus, versionColor))
+        
+        # Line 2 (authentication label red if open, green if credentials required)
+        dirPortLabel = "Dir Port: %s, " % self.vals["DirPort"] if self.vals["DirPort"] != "0" else ""
+        
+        if self.vals["IsPasswordAuthSet"]: controlPortAuthLabel = "password"
+        elif self.vals["IsCookieAuthSet"]: controlPortAuthLabel = "cookie"
+        else: controlPortAuthLabel = "open"
+        controlPortAuthColor = "red" if controlPortAuthLabel == "open" else "green"
+        
+        labelStart = "%s - %s:%s, %sControl Port (" % (self.vals["Nickname"], self.vals["address"], self.vals["ORPort"], dirPortLabel)
+        self.addfstr(1, 0, "%s<%s>%s</%s>): %s" % (labelStart, controlPortAuthColor, controlPortAuthLabel, controlPortAuthColor, self.vals["ControlPort"]))
+        
+        # Line 3 (system usage info) - line 1 right if wide
+        y, x = 0 if self.isWide else 2, 75 if self.isWide else 0
+        self.addstr(y, x, "cpu: %s%%" % self.vals["%cpu"])
+        self.addstr(y, x + 13, "mem: %s (%s%%)" % (util.getSizeLabel(int(self.vals["rss"]) * 1024), self.vals["%mem"]))
+        self.addstr(y, x + 34, "pid: %s" % (self.vals["pid"] if self.vals["etime"] else ""))
+        self.addstr(y, x + 47, "uptime: %s" % self.vals["etime"])
+        
+        # Line 4 (fingerprint) - line 2 right if wide
+        y, x = 1 if self.isWide else 3, 75 if self.isWide else 0
+        self.addstr(y, x, "fingerprint: %s" % self.vals["fingerprint"])
+        
+        # Line 5 (flags) - line 3 left if wide
+        flagLine = "flags: "
+        for flag in self.vals["flags"]:
+          flagColor = FLAG_COLORS[flag] if flag in FLAG_COLORS.keys() else "white"
+          flagLine += "<b><%s>%s</%s></b>, " % (flagColor, flag, flagColor)
+        
+        if len(self.vals["flags"]) > 0: flagLine = flagLine[:-2]
+        self.addfstr(2 if self.isWide else 4, 0, flagLine)
+        
+        # Line 3 right (exit policy) - not present if not wide
+        if self.isWide:
+          exitPolicy = self.vals["ExitPolicy"]
+          
+          # adds note when default exit policy is appended
+          if exitPolicy == None: exitPolicy = "<default>"
+          elif not exitPolicy.endswith("accept *:*") and not exitPolicy.endswith("reject *:*"):
+            exitPolicy += ", <default>"
+          
+          policies = exitPolicy.split(", ")
+          
+          # color codes accepts to be green, rejects to be red, and default marker to be cyan
+          isSimple = len(policies) <= 2 # if policy is short then it's kept verbose, otherwise 'accept' and 'reject' keywords removed
+          for i in range(len(policies)):
+            policy = policies[i].strip()
+            displayedPolicy = policy if isSimple else policy.replace("accept", "").replace("reject", "").strip()
+            if policy.startswith("accept"): policy = "<green><b>%s</b></green>" % displayedPolicy
+            elif policy.startswith("reject"): policy = "<red><b>%s</b></red>" % displayedPolicy
+            elif policy.startswith("<default>"): policy = "<cyan><b>%s</b></cyan>" % displayedPolicy
+            policies[i] = policy
+          exitPolicy = ", ".join(policies)
+          
+          self.addfstr(2, 75, "exit policy: %s" % exitPolicy)
+        
+        self.refresh()
+      finally:
+        self.lock.release()
   
   def setPaused(self, isPause):
     """
@@ -155,6 +159,8 @@
     
     infoFields = ["address", "fingerprint"] # keys for which get_info will be called
     if len(self.vals) <= 1 or forceReload:
+      isConnClosed = False
+      
       # first call (only contasns 'pid' mapping) - retrieve static params
       infoFields += ["version", "status/version/current"]
       
@@ -166,14 +172,24 @@
       
       # parameters from the user's torrc
       configFields = ["Nickname", "ORPort", "DirPort", "ControlPort", "ExitPolicy"]
-      self.vals.update(dict([(key, self.conn.get_option(key)[0][1]) for key in configFields]))
+      try: self.vals.update(dict([(key, self.conn.get_option(key)[0][1]) for key in configFields]))
+      except TorCtl.TorCtlClosed: isConnClosed = True
       
       # simply keeps booleans for if authentication info is set
-      self.vals["IsPasswordAuthSet"] = not self.conn.get_option("HashedControlPassword")[0][1] == None
-      self.vals["IsCookieAuthSet"] = self.conn.get_option("CookieAuthentication")[0][1] == "1"
+      try:
+        self.vals["IsPasswordAuthSet"] = not self.conn.get_option("HashedControlPassword")[0][1] == None
+        self.vals["IsCookieAuthSet"] = self.conn.get_option("CookieAuthentication")[0][1] == "1"
+        self.vals["IsAccountingEnabled"] = self.conn.get_info('accounting/enabled')['accounting/enabled'] == "1"
+      except TorCtl.TorCtlClosed: isConnClosed = True
       
-      self.vals["IsAccountingEnabled"] = self.conn.get_info('accounting/enabled')['accounting/enabled'] == "1"
-    
+      if isConnClosed:
+        # tor connection closed - keep old values if available, otherwise set to empty string / false
+        for field in configFields:
+          if field not in self.vals: self.vals[field] = ""
+        
+        for field in ["IsPasswordAuthSet", "IsCookieAuthSet", "IsAccountingEnabled"]:
+          if field not in self.vals: self.vals[field] = False
+      
     # gets parameters that throw errors if unavailable
     for param in infoFields:
       try: self.vals.update(self.conn.get_info(param))
@@ -191,7 +207,7 @@
     psParams = ["%cpu", "rss", "%mem", "etime"]
     if self.vals["pid"]:
       # ps call provides header followed by params for tor
-      psCall = os.popen('ps -p %s -o %s' % (self.vals["pid"], ",".join(psParams)))
+      psCall = os.popen('ps -p %s -o %s  2> /dev/null' % (self.vals["pid"], ",".join(psParams)))
       
       try: sampling = psCall.read().strip().split()[len(psParams):]
       except IOError: sampling = [] # ps call failed

Modified: arm/trunk/interface/hostnameResolver.py
===================================================================
--- arm/trunk/interface/hostnameResolver.py	2009-09-27 07:58:53 UTC (rev 20673)
+++ arm/trunk/interface/hostnameResolver.py	2009-09-27 08:50:41 UTC (rev 20674)
@@ -118,10 +118,12 @@
       except Queue.Empty: continue
       
       resolutionFailed = False            # if true don't cache results
-      hostCall = os.popen("host %s" % ipAddr)
+      hostCall = os.popen("host %s 2> /dev/null" % ipAddr)
       
       try:
-        hostname = hostCall.read().split()[-1:][0]
+        hostname = hostCall.read()
+        if hostname: hostname = hostname.split()[-1:][0]
+        else: raise IOError # call failed ('host' command probably unavailable)
         
         if hostname == "reached":
           # got message: ";; connection timed out; no servers could be reached"

Deleted: arm/trunk/readme.txt
===================================================================
--- arm/trunk/readme.txt	2009-09-27 07:58:53 UTC (rev 20673)
+++ arm/trunk/readme.txt	2009-09-27 08:50:41 UTC (rev 20674)
@@ -1,84 +0,0 @@
-arm (anonymizing relay monitor) - Terminal status monitor for Tor relays.
-Developed by Damian Johnson (www.atagar.com - atagar1 at gmail.com)
-All code under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
-Project page: www.atagar.com/arm
-
-Description:
-Command line application for monitoring Tor relays, providing real time status 
-information such as the current configuration, bandwidth usage, message log, 
-connections, etc. This uses a curses interface much like 'top' does for system 
-usage. The application is intended for command-line aficionados, ssh 
-connections, and anyone stuck with a tty terminal for checking their relay's 
-status. Releases should be stable so if you manage to make it crash (or have a 
-feature request) then please let me know!
-
-The project was originally proposed in 2008 by Jacob and Karsten:
-  http://archives.seul.org/or/dev/Jan-2008/msg00005.html
-
-An interview by Brenno Winter discussing the project is available at:
-  http://www.atagar.com/arm/HFM_INT_0001.mp3
-
-Requirements:
-Python 2.5
-TorCtl (retrieved in svn checkout)
-Common *nix commands including: ps, pidof, host, and netstat
-Tor is running with an available control port. This means either...
-  ... starting Tor with '--controlport <PORT>'
-  ... or including 'ControlPort <PORT>' in your torrc
-
-This is started via 'arm' (use the '--help' argument for usage).
-
-FAQ:
-> Why is it called 'arm'?
-
-Simple - because it makes the command short and memorable. Terminal 
-applications need to be easy to type (like 'top', 'ssh', etc), and anything 
-longer is just begging command-line aficionados to alias it down. I chose the 
-meaning of the acronym ('anonymizing relay monitor') afterward.
-
-> If you're listing connections then what about exit nodes? Won't this include 
-people's traffic?
-
-While arm isn't intended to be a sniffer it does provide real time connection 
-data which, for exit nodes, includes the endpoints being visited through you. 
-Unfortunately this is pretty unavoidable. The control port doesn't provide a 
-means of distinguishing those connections and trying to figure it out by 
-correlating against consensus data has proved pretty inaccurate.
-
-That said, this really isn't much of a concern. For Tor users the real threats 
-come from things like Wireshark and MITM attacks on their unencrypted traffic. 
-Simply seeing an unknown individual's endpoints is no great feat in itself. 
-Just attach netstat to a cron job and voilà! You've got a sniffer that's just 
-as mighty as arm.
-
-> Is it harmful to share the information provided by arm?
-
-Not really, but it's discouraged. The original plan for arm included a special 
-emphasis that it wouldn't log any data. The reason is that if a large number 
-of relay operators published the details of their connections then correlation 
-attacks could break Tor user's anonymity. Just show some moderation in what 
-you share and it should be fine.
-
-> Is there any chance that arm will leak data?
-
-Yes - arm is a passive listener with one exception. The second page 
-(connections) provides the hostnames of Tor relays you're connected to. This 
-means reverse DNS lookups which, if monitored, could leak your current 
-connections to an eavesdropper. However, lookups are only made upon request 
-(when showing connection details or listing connections by hostname) and you 
-can disable lookups entirely with 'r' - see the page's help for the current 
-status.
-
-That said, this is not a terribly big whoop. ISPs and anyone sniffing your 
-connection already has this data - the only difference is that instead of 
-saying "I am talking to x" you're saying "I'm talking to x, who's x?", meaning 
-the resolver's also aware of who they are.
-
-> When arm starts it gives "Unable to resolve tor pid, abandoning connection 
-listing"... why?
-
-If you're running multiple instances of tor then arm needs to figure out which 
-pid belongs to the open control port. If it's running as a different user 
-(such as being in a chroot jail) then it's probably failing due to permission 
-issues. Arm still runs, just no connection listing or ps stats.
-



More information about the tor-commits mailing list