[or-cvs] r21580: {arm} Had enough of a siesta - getting back into development begin (in arm/trunk: . interface)
Damian Johnson
atagar1 at gmail.com
Mon Feb 8 01:47:07 UTC 2010
Author: atagar
Date: 2010-02-08 01:47:07 +0000 (Mon, 08 Feb 2010)
New Revision: 21580
Modified:
arm/trunk/ChangeLog
arm/trunk/arm.py
arm/trunk/interface/controller.py
arm/trunk/interface/logPanel.py
Log:
Had enough of a siesta - getting back into development beginning with a rewrite if the starter.
added: made authentication a little smarter, using PROTOCOLINFO to autodetect authentication type and cookie location
change: made 'blind mode' (disables connection queries) a startup option rather than flag in source (request by Sebastian)
change: all log events (including arm) are now set via character flags, with TorCtl events log as their own toggleable type
change: starting log label with runlevel events, condensing if logging a range
change: simplifying command line parsing via getopt
fix: blind mode now prevents all netstats (including connection counts and halting resolver thread), improving performance
Modified: arm/trunk/ChangeLog
===================================================================
--- arm/trunk/ChangeLog 2010-02-07 15:19:23 UTC (rev 21579)
+++ arm/trunk/ChangeLog 2010-02-08 01:47:07 UTC (rev 21580)
@@ -1,6 +1,16 @@
CHANGE LOG
-11/29/09 - version 1.3.0
+2/7/10 - version 1.3.1
+Had enough of a siesta - getting back into development beginning with a rewrite if the starter.
+
+ * added: made authentication a little smarter, using PROTOCOLINFO to autodetect authentication type and cookie location
+ * change: made 'blind mode' (disables connection queries) a startup option rather than flag in source (request by Sebastian)
+ * change: all log events (including arm) are now set via character flags, with TorCtl events log as their own toggleable type
+ * change: starting log label with runlevel events, condensing if logging a range
+ * change: simplifying command line parsing via getopt
+ * fix: blind mode now prevents all netstats (including connection counts and halting resolver thread), improving performance
+
+11/29/09 - version 1.3.0 (r21062)
Weekend bugfix bundle.
* added: most commands can be immediately executed from the help page (feature request by arma)
Modified: arm/trunk/arm.py
===================================================================
--- arm/trunk/arm.py 2010-02-07 15:19:23 UTC (rev 21579)
+++ arm/trunk/arm.py 2010-02-08 01:47:07 UTC (rev 21580)
@@ -9,8 +9,8 @@
"""
import sys
-import os
import socket
+import getopt
import getpass
from TorCtl import TorCtl
@@ -19,120 +19,31 @@
from interface import controller
from interface import logPanel
-VERSION = "1.3.0"
-LAST_MODIFIED = "Nov 29, 2009"
+VERSION = "1.3.1"
+LAST_MODIFIED = "Feb 7, 2010"
DEFAULT_CONTROL_ADDR = "127.0.0.1"
DEFAULT_CONTROL_PORT = 9051
-DEFAULT_AUTH_COOKIE = os.path.expanduser("~/.tor/control_auth_cookie") # TODO: Check if this is valid for macs
-DEFAULT_LOGGED_EVENTS = "nwe" # NOTICE, WARN, ERR
+DEFAULT_LOGGED_EVENTS = "N3" # tor and arm NOTICE, WARN, and ERR events
-NO_AUTH, COOKIE_AUTH, PASSWORD_AUTH = range(3) # enums for authentication type
-
+OPT = "i:p:be:vh"
+OPT_EXPANDED = ["interface=", "password=", "blind", "event=", "version", "help"]
HELP_TEXT = """Usage arm [OPTION]
Terminal status monitor for Tor relays.
-i, --interface [ADDRESS:]PORT change control interface from %s:%i
- -c, --cookie[=PATH] authenticates using cookie, PATH defaults to
- '%s'
- -p, --password[=PASSWORD] authenticates using password, prompting
- without terminal echo if not provided
- -e, --event=[EVENT FLAGS] event types in message log (default: %s)
+ -p, --password PASSWORD authenticate using password (skip prompt)
+ -b, --blind disable connection lookups
+ -e, --event EVENT_FLAGS event types in message log (default: %s)
%s
-v, --version provides version information
-h, --help presents this help
Example:
-arm -c authenticate using the default cookie
-arm -i 1643 -p prompt for password using control port 1643
+arm -b -i 1643 hide connection data, attaching to control port 1643
arm -e=we -p=nemesis use password 'nemesis' with 'WARN'/'ERR' events
-""" % (DEFAULT_CONTROL_ADDR, DEFAULT_CONTROL_PORT, DEFAULT_AUTH_COOKIE, DEFAULT_LOGGED_EVENTS, logPanel.EVENT_LISTING)
+""" % (DEFAULT_CONTROL_ADDR, DEFAULT_CONTROL_PORT, DEFAULT_LOGGED_EVENTS, logPanel.EVENT_LISTING)
-class Input:
- "Collection of the user's command line input"
-
- def __init__(self, args):
- self.controlAddr = DEFAULT_CONTROL_ADDR # controller interface IP address
- self.controlPort = DEFAULT_CONTROL_PORT # controller interface port
- self.authType = NO_AUTH # type of authentication used
- self.authCookieLoc = DEFAULT_AUTH_COOKIE # location of authentication cookie
- self.authPassword = "" # authentication password
- self.loggedEvents = DEFAULT_LOGGED_EVENTS # flags for event types in message log
- self.isValid = True # determines if the program should run
- self.printVersion = False # prints version then quits
- self.printHelp = False # prints help then quits
- self._parseArgs(args)
-
- def _parseArgs(self, args):
- """
- Recursively parses arguments, populating parameters and checking input
- validity. This does not check if options are defined multiple times.
- """
-
- if len(args) == 0: return
- elif args[0] == "-i" or args[0] == "--interface":
- # defines control interface address/port
- if len(args) >= 2:
- interfaceArg = args[1]
-
- try:
- divIndex = interfaceArg.find(":")
-
- if divIndex == -1:
- self.controlAddr = DEFAULT_CONTROL_ADDR
- self.controlPort = int(interfaceArg)
- else:
- self.controlAddr = interfaceArg[0:divIndex]
- if not isValidIpAddr(self.controlAddr): raise AssertionError()
- self.controlPort = int(interfaceArg[divIndex + 1:])
- self._parseArgs(args[2:])
- except ValueError:
- print "'%s' isn't a valid interface" % interfaceArg
- self.isValid = False
- except AssertionError:
- print "'%s' isn't a valid IP address" % self.controlAddr
- self.isValid = False
- else:
- print "%s argument provided without defining an interface" % args[0]
- self.isValid = False
-
- elif args[0] == "-c" or args[0].startswith("-c=") or args[0] == "--cookie" or args[0].startswith("--cookie="):
- # set to use cookie authentication (and possibly define location)
- self.authType = COOKIE_AUTH
-
- # sets authentication path if provided
- if args[0].startswith("-c="):
- self.authCookieLoc = args[0][3:]
- elif args[0].startswith("--cookie="):
- self.authCookieLoc = args[0][9:]
-
- self._parseArgs(args[1:])
- elif args[0] == "-p" or args[0].startswith("-p=") or args[0] == "--password" or args[0].startswith("--password="):
- # set to use password authentication
- self.authType = PASSWORD_AUTH
-
- # sets authentication password if provided
- if args[0].startswith("-p="):
- self.authPassword = args[0][3:]
- elif args[0].startswith("--password="):
- self.authPassword = args[0][11:]
-
- self._parseArgs(args[1:])
- elif args[0].startswith("-e=") or args[0].startswith("--event="):
- # set event flags
- if args[0].startswith("-e="): self.loggedEvents = args[0][3:]
- else: self.loggedEvents = args[0][8:]
- self._parseArgs(args[1:])
- elif args[0] == "-v" or args[0] == "--version":
- self.printVersion = True
- self._parseArgs(args[1:])
- elif args[0] == "-h" or args[0] == "--help":
- self.printHelp = True
- self._parseArgs(args[1:])
- else:
- print "Unrecognized command: " + args[0]
- self.isValid = False
-
def isValidIpAddr(ipStr):
"""
Returns true if input is a valid IPv4 address, false otherwise.
@@ -157,61 +68,127 @@
return True
if __name__ == '__main__':
- # parses user input, quitting if there's a problem
- input = Input(sys.argv[1:])
- if not input.isValid: sys.exit()
+ controlAddr = DEFAULT_CONTROL_ADDR # controller interface IP address
+ controlPort = DEFAULT_CONTROL_PORT # controller interface port
+ authPassword = "" # authentication password (prompts if unset and needed)
+ isBlindMode = False # allows connection lookups to be disabled
+ loggedEvents = DEFAULT_LOGGED_EVENTS # flags for event types in message log
- # if help or version flags are set then prints and quits
- if input.printHelp:
- print HELP_TEXT
+ # parses user input, noting any issues
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], OPT, OPT_EXPANDED)
+ except getopt.GetoptError, exc:
+ print str(exc) + " (for usage provide --help)"
sys.exit()
- elif input.printVersion:
- print "arm version %s (released %s)\n" % (VERSION, LAST_MODIFIED)
- sys.exit()
- # validates that cookie authentication path exists
- if input.authType == COOKIE_AUTH and not os.path.exists(input.authCookieLoc):
- print "Authentication cookie doesn't exist: %s" % input.authCookieLoc
- sys.exit()
+ for opt, arg in opts:
+ if opt in ("-i", "--interface"):
+ # defines control interface address/port
+ try:
+ divIndex = arg.find(":")
+
+ if divIndex == -1:
+ controlPort = int(arg)
+ else:
+ controlAddr = arg[0:divIndex]
+ controlPort = int(arg[divIndex + 1:])
+
+ # validates that input is a valid ip address and port
+ if divIndex != -1 and not isValidIpAddr(controlAddr):
+ raise AssertionError("'%s' isn't a valid IP address" % controlAddr)
+ elif controlPort < 0 or controlPort > 65535:
+ raise AssertionError("'%s' isn't a valid port number (ports range 0-65535)" % controlPort)
+ except ValueError:
+ print "'%s' isn't a valid port number" % arg
+ sys.exit()
+ except AssertionError, exc:
+ print exc
+ sys.exit()
+ elif opt in ("-p", "--password"): authPassword = arg # sets authentication password
+ elif opt in ("-b", "--blind"): isBlindMode = True # prevents connection lookups
+ elif opt in ("-e", "--event"): loggedEvents = arg # set event flags
+ elif opt in ("-v", "--version"):
+ print "arm version %s (released %s)\n" % (VERSION, LAST_MODIFIED)
+ sys.exit()
+ elif opt in ("-h", "--help"):
+ print HELP_TEXT
+ sys.exit()
- # promts for password if not provided
- if input.authType == PASSWORD_AUTH and input.authPassword == "":
- input.authPassword = getpass.getpass()
-
- # validates and expands logged event flags
+ # validates and expands log event flags
try:
- expandedEvents = logPanel.expandEvents(input.loggedEvents)
+ expandedEvents = logPanel.expandEvents(loggedEvents)
except ValueError, exc:
for flag in str(exc):
print "Unrecognized event flag: %s" % flag
sys.exit()
- # temporarily disables TorCtl logging to prevent issues from going to stdout when starting
+ # temporarily disables TorCtl logging to prevent issues from going to stdout while starting
TorUtil.loglevel = "NONE"
# attempts to open a socket to the tor server
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
- s.connect((input.controlAddr, input.controlPort))
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect((controlAddr, controlPort))
conn = TorCtl.Connection(s)
+ except socket.error, exc:
+ if str(exc) == "[Errno 111] Connection refused":
+ # most common case - tor control port isn't available
+ print "Connection refused. Is the ControlPort enabled?"
+ else:
+ # less common issue - provide exc message
+ print "Failed to establish socket: %s" % exc
- # provides authentication credentials to the control port
- if input.authType == NO_AUTH:
+ sys.exit()
+
+ # check PROTOCOLINFO for authentication type
+ try:
+ authInfo = conn.sendAndRecv("PROTOCOLINFO\r\n")[1][1]
+ except TorCtl.ErrorReply, exc:
+ print "Unable to query PROTOCOLINFO for authentication type: %s" % exc
+ sys.exit()
+
+ try:
+ if authInfo.startswith("AUTH METHODS=NULL"):
+ # no authentication required
conn.authenticate("")
- elif input.authType == COOKIE_AUTH:
- authCookie = open(input.authCookieLoc)
- conn.authenticate_cookie(authCookie)
- authCookie.close()
+ elif authInfo.startswith("AUTH METHODS=HASHEDPASSWORD"):
+ # password authentication, promts for password if it wasn't provided
+ if not authPassword: authPassword = getpass.getpass()
+ conn.authenticate(authPassword)
+ elif authInfo.startswith("AUTH METHODS=COOKIE"):
+ # cookie authtication, parses path to authentication cookie
+ start = authInfo.find("COOKIEFILE=\"") + 12
+ end = authInfo[start:].find("\"")
+ authCookiePath = authInfo[start:start + end]
+
+ try:
+ authCookie = open(authCookiePath, "r")
+ conn.authenticate_cookie(authCookie)
+ authCookie.close()
+ except IOError, exc:
+ # cleaner message for common errors
+ issue = None
+ if str(exc).startswith("[Errno 13] Permission denied"): issue = "permission denied"
+ elif str(exc).startswith("[Errno 2] No such file or directory"): issue = "file doesn't exist"
+
+ # if problem's recognized give concise message, otherwise print exception string
+ if issue: print "Failed to read authentication cookie (%s): %s" % (issue, authCookiePath)
+ else: print "Failed to read authentication cookie: %s" % exc
+
+ sys.exit()
else:
- assert input.authType == PASSWORD_AUTH, "Invalid value in input.authType enum: " + str(input.authType)
- conn.authenticate(input.authPassword)
- except socket.error, exc:
- print "Is the ControlPort enabled? Connection failed: %s" % exc
- sys.exit()
+ # authentication type unrecognized (probably a new addition to the controlSpec)
+ print "Unrecognized authentication type: %s" % authInfo
+ sys.exit()
except TorCtl.ErrorReply, exc:
- print "Connection failed: %s" % exc
+ # authentication failed
+ issue = str(exc)
+ if str(exc).startswith("515 Authentication failed: Password did not match"): issue = "password incorrect"
+ if str(exc) == "515 Authentication failed: Wrong length on authentication cookie.": issue = "cookie value incorrect"
+
+ print "Unable to authenticate: %s" % issue
sys.exit()
- controller.startTorMonitor(conn, expandedEvents)
+ controller.startTorMonitor(conn, expandedEvents, isBlindMode)
conn.close()
Modified: arm/trunk/interface/controller.py
===================================================================
--- arm/trunk/interface/controller.py 2010-02-07 15:19:23 UTC (rev 21579)
+++ arm/trunk/interface/controller.py 2010-02-08 01:47:07 UTC (rev 21580)
@@ -31,7 +31,6 @@
import connCountMonitor
CONFIRM_QUIT = True
-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)
@@ -54,13 +53,14 @@
class ControlPanel(util.Panel):
""" Draws single line label for interface controls. """
- def __init__(self, lock, resolver):
+ def __init__(self, lock, resolver, isBlindMode):
util.Panel.__init__(self, lock, 1)
self.msgText = CTL_HELP # message text to be displyed
self.msgAttr = curses.A_NORMAL # formatting attributes
self.page = 1 # page number currently being displayed
self.resolver = resolver # dns resolution thread
- self.resolvingCounter = -1 # count of resolver when starting (-1 if we aren't working on a batch)
+ self.resolvingCounter = -1 # count of resolver when starting (-1 if we aren't working on a batch)
+ self.isBlindMode = isBlindMode
def setMsg(self, msgText, msgAttr=curses.A_NORMAL):
"""
@@ -109,7 +109,7 @@
currentPage = self.page
pageCount = len(PAGES)
- if DISABLE_CONNECTIONS_PAGE:
+ if self.isBlindMode:
if currentPage >= 2: currentPage -= 1
pageCount -= 1
@@ -210,9 +210,13 @@
# adds events used for panels to function if not already included
connEvents = loggedEvents.union(set(REQ_EVENTS))
- # removes UNKNOWN since not an actual event type
- connEvents.discard("UNKNOWN")
+ # removes special types only used in arm (UNKNOWN, TORCTL, ARM_DEBUG, etc)
+ toDiscard = []
+ for event in connEvents:
+ if event not in logPanel.TOR_EVENT_TYPES.values(): toDiscard += [event]
+ for event in toDiscard: connEvents.discard(event)
+
while not eventsSet:
try:
conn.set_events(connEvents)
@@ -231,7 +235,7 @@
if eventType in REQ_EVENTS:
if eventType == "BW": msg = "(bandwidth graph won't function)"
- elif eventType in ("NEWDESC", "NEWCONSENSUS"): msg = "(connections listing can't register consensus changes)"
+ elif eventType in ("NEWDESC", "NEWCONSENSUS") and not isBlindMode: msg = "(connections listing can't register consensus changes)"
else: msg = ""
logListener.monitor_event("ERR", "Unsupported event type: %s %s" % (eventType, msg))
else: logListener.monitor_event("WARN", "Unsupported event type: %s" % eventType)
@@ -242,7 +246,7 @@
loggedEvents.sort() # alphabetizes
return loggedEvents
-def drawTorMonitor(stdscr, conn, loggedEvents):
+def drawTorMonitor(stdscr, conn, loggedEvents, isBlindMode):
"""
Starts arm interface reflecting information on provided control port.
@@ -314,14 +318,14 @@
# starts thread for processing netstat queries
connResolutionThread = connResolver.ConnResolver(conn, torPid, panels["log"])
- connResolutionThread.start()
+ if not isBlindMode: connResolutionThread.start()
panels["conn"] = connPanel.ConnPanel(cursesLock, conn, connResolutionThread, panels["log"])
- panels["control"] = ControlPanel(cursesLock, panels["conn"].resolver)
+ panels["control"] = ControlPanel(cursesLock, panels["conn"].resolver, isBlindMode)
panels["torrc"] = confPanel.ConfPanel(cursesLock, confLocation, conn, panels["log"])
# prevents netstat calls by connPanel if not being used
- if DISABLE_CONNECTIONS_PAGE: panels["conn"].isDisabled = True
+ if isBlindMode: 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")
@@ -329,7 +333,7 @@
# statistical monitors for graph
panels["graph"].addStats("bandwidth", bandwidthMonitor.BandwidthMonitor(conn))
panels["graph"].addStats("system resources", cpuMemMonitor.CpuMemMonitor(panels["header"]))
- panels["graph"].addStats("connections", connCountMonitor.ConnCountMonitor(conn, connResolutionThread))
+ if not isBlindMode: panels["graph"].addStats("connections", connCountMonitor.ConnCountMonitor(conn, connResolutionThread))
panels["graph"].setStats("bandwidth")
# listeners that update bandwidth and log panels with Tor status
@@ -337,7 +341,7 @@
conn.add_event_listener(panels["log"])
conn.add_event_listener(panels["graph"].stats["bandwidth"])
conn.add_event_listener(panels["graph"].stats["system resources"])
- conn.add_event_listener(panels["graph"].stats["connections"])
+ if not isBlindMode: conn.add_event_listener(panels["graph"].stats["connections"])
conn.add_event_listener(panels["conn"])
conn.add_event_listener(sighupTracker)
@@ -382,7 +386,7 @@
# other panels that use torrc data
panels["conn"].resetOptions()
- panels["graph"].stats["connections"].resetOptions(conn)
+ if not isBlindMode: panels["graph"].stats["connections"].resetOptions(conn)
panels["graph"].stats["bandwidth"].resetOptions()
panels["torrc"].reset()
@@ -474,7 +478,7 @@
else: page = (page + 1) % len(PAGES)
# skip connections listing if it's disabled
- if page == 1 and DISABLE_CONNECTIONS_PAGE:
+ if page == 1 and isBlindMode:
if key == curses.KEY_LEFT: page = (page - 1) % len(PAGES)
else: page = (page + 1) % len(PAGES)
@@ -516,13 +520,8 @@
popup.addfstr(2, 41, "<b>d</b>: file descriptors")
popup.addfstr(3, 2, "<b>e</b>: change logged events")
- runlevelEventsLabel = "arm and tor"
- if panels["log"].runlevelTypes == logPanel.RUNLEVEL_TOR_ONLY: runlevelEventsLabel = "tor only"
- elif panels["log"].runlevelTypes == logPanel.RUNLEVEL_ARM_ONLY: runlevelEventsLabel = "arm only"
- popup.addfstr(3, 41, "<b>r</b>: logged runlevels (<b>%s</b>)" % runlevelEventsLabel)
-
regexLabel = "enabled" if panels["log"].regexFilter else "disabled"
- popup.addfstr(4, 2, "<b>f</b>: log regex filter (<b>%s</b>)" % regexLabel)
+ popup.addfstr(3, 41, "<b>f</b>: log regex filter (<b>%s</b>)" % regexLabel)
pageOverrideKeys = (ord('s'), ord('i'), ord('d'), ord('e'), ord('r'), ord('f'))
if page == 1:
@@ -765,25 +764,6 @@
# reverts changes made for popup
panels["graph"].showLabel = True
setPauseState(panels, isPaused, page)
- elif page == 0 and (key == ord('r') or key == ord('R')):
- # provides menu to pick the type of runlevel events to log
- options = ["tor only", "arm only", "arm and tor"]
- initialSelection = panels["log"].runlevelTypes
-
- # hides top label of the graph panel and pauses panels
- if panels["graph"].currentDisplay:
- panels["graph"].showLabel = False
- panels["graph"].redraw()
- setPauseState(panels, isPaused, page, True)
-
- selection = showMenu(stdscr, panels["popup"], "Logged Runlevels:", options, initialSelection)
-
- # reverts changes made for popup
- panels["graph"].showLabel = True
- setPauseState(panels, isPaused, page)
-
- # applies new setting
- if selection != -1: panels["log"].runlevelTypes = selection
elif key == 27 and panels["conn"].listingType == connPanel.LIST_HOSTNAME and panels["control"].resolvingCounter != -1:
# canceling hostname resolution (esc on any page)
panels["conn"].listingType = connPanel.LIST_IP
@@ -1106,6 +1086,6 @@
elif page == 2:
panels["torrc"].handleKey(key)
-def startTorMonitor(conn, loggedEvents):
- curses.wrapper(drawTorMonitor, conn, loggedEvents)
+def startTorMonitor(conn, loggedEvents, isBlindMode):
+ curses.wrapper(drawTorMonitor, conn, loggedEvents, isBlindMode)
Modified: arm/trunk/interface/logPanel.py
===================================================================
--- arm/trunk/interface/logPanel.py 2010-02-07 15:19:23 UTC (rev 21579)
+++ arm/trunk/interface/logPanel.py 2010-02-08 01:47:07 UTC (rev 21580)
@@ -17,13 +17,12 @@
PRE_POPULATE_MAX_LIMIT = 5000 # limit for NOTICE - ERR (since most lines are skipped)
MAX_LOG_ENTRIES = 1000 # size of log buffer (max number of entries)
RUNLEVEL_EVENT_COLOR = {"DEBUG": "magenta", "INFO": "blue", "NOTICE": "green", "WARN": "yellow", "ERR": "red"}
-RUNLEVEL_TOR_ONLY, RUNLEVEL_ARM_ONLY, RUNLEVEL_BOTH = range(3)
-EVENT_TYPES = {
- "d": "DEBUG", "a": "ADDRMAP", "l": "NEWDESC", "v": "AUTHDIR_NEWDESCS",
- "i": "INFO", "b": "BW", "m": "NS", "x": "STATUS_GENERAL",
- "n": "NOTICE", "c": "CIRC", "o": "ORCONN", "y": "STATUS_CLIENT",
- "w": "WARN", "f": "DESCCHANGED", "s": "STREAM", "z": "STATUS_SERVER",
+TOR_EVENT_TYPES = {
+ "d": "DEBUG", "a": "ADDRMAP", "l": "NEWDESC", "v": "AUTHDIR_NEWDESCS",
+ "i": "INFO", "b": "BW", "m": "NS", "x": "STATUS_GENERAL",
+ "n": "NOTICE", "c": "CIRC", "o": "ORCONN", "y": "STATUS_CLIENT",
+ "w": "WARN", "f": "DESCCHANGED", "s": "STREAM", "z": "STATUS_SERVER",
"e": "ERR", "g": "GUARD", "t": "STREAM_BW",
"k": "NEWCONSENSUS", "u": "CLIENTS_SEEN"}
@@ -31,44 +30,54 @@
i INFO b BW m NS x STATUS_GENERAL
n NOTICE c CIRC o ORCONN y STATUS_CLIENT
w WARN f DESCCHANGED s STREAM z STATUS_SERVER
- e ERR g GUARD t STREAM_BW
- k NEWCONSENSUS u CLIENTS_SEEN
- Aliases: A All Events X No Events U Unknown Events
- DINWE Runlevel and higher severity"""
+ e ERR g GUARD t STREAM_BW A All Events
+ k NEWCONSENSUS u CLIENTS_SEEN X No Events
+ DINWE Runlevel and higher severity C TorCtl Events
+ 12345 ARM runlevel and higher severity U Unknown Events"""
TOR_CTL_CLOSE_MSG = "Tor closed control connection. Exiting event thread."
def expandEvents(eventAbbr):
"""
Expands event abbreviations to their full names. Beside mappings privided in
- EVENT_TYPES this recognizes:
- A - alias for all events
- U - "UNKNOWN" events
- R - alias for runtime events (DEBUG, INFO, NOTICE, WARN, ERR)
+ TOR_EVENT_TYPES this recognizes the following special events and aliases:
+ C - TORCTL runlevel events
+ U - UKNOWN events
+ A - all events
+ X - no events
+ DINWE - runlevel and higher
+ 12345 - arm runlevel and higher (ARM_DEBUG - ARM_ERR)
Raises ValueError with invalid input if any part isn't recognized.
- Example:
+ Examples:
"inUt" -> ["INFO", "NOTICE", "UNKNOWN", "STREAM_BW"]
+ "N4" -> ["NOTICE", "WARN", "ERR", "ARM_WARN", "ARM_ERR"]
+ "cfX" -> []
"""
expandedEvents = set()
invalidFlags = ""
for flag in eventAbbr:
if flag == "A":
- expandedEvents = set(EVENT_TYPES.values())
- expandedEvents.add("UNKNOWN")
+ expandedEvents = set(TOR_EVENT_TYPES.values() + ["ARM_DEBUG", "ARM_INFO", "ARM_NOTICE", "ARM_WARN", "ARM_ERR"])
break
elif flag == "X":
expandedEvents = set()
break
+ elif flag == "C": expandedEvents.add("TORCTL")
elif flag == "U": expandedEvents.add("UNKNOWN")
elif flag == "D": expandedEvents = expandedEvents.union(set(["DEBUG", "INFO", "NOTICE", "WARN", "ERR"]))
elif flag == "I": expandedEvents = expandedEvents.union(set(["INFO", "NOTICE", "WARN", "ERR"]))
elif flag == "N": expandedEvents = expandedEvents.union(set(["NOTICE", "WARN", "ERR"]))
elif flag == "W": expandedEvents = expandedEvents.union(set(["WARN", "ERR"]))
elif flag == "E": expandedEvents.add("ERR")
- elif flag in EVENT_TYPES:
- expandedEvents.add(EVENT_TYPES[flag])
+ elif flag == "1": expandedEvents = expandedEvents.union(set(["ARM_DEBUG", "ARM_INFO", "ARM_NOTICE", "ARM_WARN", "ARM_ERR"]))
+ elif flag == "2": expandedEvents = expandedEvents.union(set(["ARM_INFO", "ARM_NOTICE", "ARM_WARN", "ARM_ERR"]))
+ elif flag == "3": expandedEvents = expandedEvents.union(set(["ARM_NOTICE", "ARM_WARN", "ARM_ERR"]))
+ elif flag == "4": expandedEvents = expandedEvents.union(set(["ARM_WARN", "ARM_ERR"]))
+ elif flag == "5": expandedEvents.add("ARM_ERR")
+ elif flag in TOR_EVENT_TYPES:
+ expandedEvents.add(TOR_EVENT_TYPES[flag])
else:
invalidFlags += flag
@@ -91,7 +100,6 @@
self.lastHeartbeat = time.time() # time of last event
self.regexFilter = None # filter for presented log events (no filtering if None)
self.eventTimeOverwrite = None # replaces time for further events with this (uses time it occures if None)
- self.runlevelTypes = RUNLEVEL_BOTH # types of runlevels to show (arm, tor, or both)
self.controlPortClosed = False # flag set if TorCtl provided notice that control port is closed
# attempts to process events from log file
@@ -193,7 +201,6 @@
if "BW" in self.loggedEvents: self.registerEvent("BW", "READ: %i, WRITTEN: %i" % (event.read, event.written), "cyan")
def msg_event(self, event):
- if not self.runlevelTypes in (RUNLEVEL_TOR_ONLY, RUNLEVEL_BOTH): return
self.registerEvent(event.level, event.msg, RUNLEVEL_EVENT_COLOR[event.level])
def new_desc_event(self, event):
@@ -223,10 +230,13 @@
if "UNKNOWN" in self.loggedEvents: self.registerEvent("UNKNOWN", event.event_string, "red")
def monitor_event(self, level, msg):
- # events provided by the arm monitor - types use the same as runlevel
- if not self.runlevelTypes in (RUNLEVEL_ARM_ONLY, RUNLEVEL_BOTH): return
- if level in self.loggedEvents: self.registerEvent("ARM-%s" % level, msg, RUNLEVEL_EVENT_COLOR[level])
+ # events provided by the arm monitor
+ if "ARM_" + level in self.loggedEvents: self.registerEvent("ARM-%s" % level, msg, RUNLEVEL_EVENT_COLOR[level])
+ def tor_ctl_event(self, level, msg):
+ # events provided by TorCtl
+ if "TORCTL" in self.loggedEvents: self.registerEvent("TORCTL-%s" % level, msg, RUNLEVEL_EVENT_COLOR[level])
+
def write(self, msg):
"""
Tracks TorCtl events. Ugly hack since TorCtl/TorUtil.py expects a file.
@@ -242,8 +252,7 @@
# TorCtl providing notice that control port is closed
self.controlPortClosed = True
self.monitor_event("NOTICE", "Tor control port closed")
- else:
- self.monitor_event(level, "TorCtl: " + msg)
+ self.tor_ctl_event(level, msg)
def flush(self): pass
@@ -295,7 +304,16 @@
# draws label - uses ellipsis if too long, for instance:
# Events (DEBUG, INFO, NOTICE, WARN...):
eventsLabel = "Events"
- eventsListing = ", ".join(self.loggedEvents)
+
+ # separates tor and arm runlevels (might be able to show as range)
+ eventsList = list(self.loggedEvents)
+ torRunlevelLabel = ", ".join(parseRunlevelRanges(eventsList, ""))
+ armRunlevelLabel = ", ".join(parseRunlevelRanges(eventsList, "ARM_"))
+
+ if armRunlevelLabel: eventsList = ["ARM " + armRunlevelLabel] + eventsList
+ if torRunlevelLabel: eventsList = [torRunlevelLabel] + eventsList
+
+ eventsListing = ", ".join(eventsList)
filterLabel = "" if not self.regexFilter else " - filter: %s" % self.regexFilter.pattern
firstLabelLen = eventsListing.find(", ")
@@ -379,6 +397,48 @@
return time.time() - self.lastHeartbeat
+def parseRunlevelRanges(eventsList, searchPrefix):
+ """
+ This parses a list of events to provide an ordered list of runlevels,
+ condensed if three or more are in a contiguous range. This removes parsed
+ runlevels from the eventsList. For instance:
+
+ eventsList = ["BW", "ARM_WARN", "ERR", "ARM_ERR", "ARM_DEBUG", "ARM_NOTICE"]
+ searchPrefix = "ARM_"
+
+ results in:
+ eventsList = ["BW", "ERR"]
+ return value is ["DEBUG", "NOTICE - ERR"]
+
+ """
+
+ # blank ending runlevel forces the break condition to be reached at the end
+ runlevels = ["DEBUG", "INFO", "NOTICE", "WARN", "ERR", ""]
+ runlevelLabels = []
+ start, end = "", ""
+ rangeLength = 0
+
+ for level in runlevels:
+ if searchPrefix + level in eventsList:
+ eventsList.remove(searchPrefix + level)
+
+ if start:
+ end = level
+ rangeLength += 1
+ else:
+ start = level
+ rangeLength = 1
+ elif rangeLength > 0:
+ # reached a break in the runlevels
+ if rangeLength == 1: runlevelLabels += [start]
+ elif rangeLength == 2: runlevelLabels += [start, end]
+ else: runlevelLabels += ["%s - %s" % (start, end)]
+
+ start, end = "", ""
+ rangeLength = 0
+
+ return runlevelLabels
+
def splitLine(message, x):
"""
Divides message into two lines, attempting to do it on a wordbreak.
More information about the tor-commits
mailing list