[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[or-cvs] r19957: {arm} Connection listing now has user configurable sort functional (arm/trunk/interface)
Author: atagar
Date: 2009-07-09 01:31:36 -0400 (Thu, 09 Jul 2009)
New Revision: 19957
Modified:
arm/trunk/interface/connPanel.py
arm/trunk/interface/controller.py
Log:
Connection listing now has user configurable sort functionality (it's actually pretty spiffy - supports secondary and tertiary sub-keys).
Modified: arm/trunk/interface/connPanel.py
===================================================================
--- arm/trunk/interface/connPanel.py 2009-07-09 02:17:47 UTC (rev 19956)
+++ arm/trunk/interface/connPanel.py 2009-07-09 05:31:36 UTC (rev 19957)
@@ -9,10 +9,29 @@
import util
-# enums for sorting types
-ORD_TYPE, ORD_FOREIGN_IP, ORD_SRC_IP, ORD_DST_IP, ORD_ALPHANUMERIC, ORD_FOREIGN_PORT, ORD_SRC_PORT, ORD_DST_PORT, ORD_COUNTRY = range(9)
-SORT_TYPES = [(ORD_TYPE, "Connection Type"), (ORD_FOREIGN_IP, "IP (Foreign)"), (ORD_SRC_IP, "IP (Source)"), (ORD_DST_IP, "IP (Dest.)"), (ORD_ALPHANUMERIC, "Alphanumeric"), (ORD_FOREIGN_PORT, "Port (Foreign)"), (ORD_SRC_PORT, "Port (Source)"), (ORD_DST_PORT, "Port (Dest.)"), (ORD_COUNTRY, "Country Code")]
+# enums for sorting types (note: ordering corresponds to SORT_TYPES for easy lookup)
+# TODO: add ORD_BANDWIDTH -> (ORD_BANDWIDTH, "Bandwidth", lambda x, y: ???)
+ORD_TYPE, ORD_FOREIGN_IP, ORD_SRC_IP, ORD_DST_IP, ORD_COUNTRY, ORD_FOREIGN_PORT, ORD_SRC_PORT, ORD_DST_PORT = range(8)
+SORT_TYPES = [(ORD_TYPE, "Connection Type",
+ lambda x, y: TYPE_WEIGHTS[x[0]] - TYPE_WEIGHTS[y[0]]),
+ (ORD_FOREIGN_IP, "IP (Foreign)",
+ lambda x, y: cmp(_ipToInt(x[3]), _ipToInt(y[3]))),
+ (ORD_SRC_IP, "IP (Source)",
+ lambda x, y: cmp(_ipToInt(x[3] if x[0] == "inbound" else x[1]), _ipToInt(y[3] if y[0] == "inbound" else y[1]))),
+ (ORD_DST_IP, "IP (Dest.)",
+ lambda x, y: cmp(_ipToInt(x[1] if x[0] == "inbound" else x[3]), _ipToInt(y[1] if y[0] == "inbound" else y[3]))),
+ (ORD_COUNTRY, "Country Code",
+ lambda x, y: cmp(x[5], y[5])),
+ (ORD_FOREIGN_PORT, "Port (Foreign)",
+ lambda x, y: int(x[4]) - int(y[4])),
+ (ORD_SRC_PORT, "Port (Source)",
+ lambda x, y: int(x[4] if x[0] == "inbound" else x[2]) - int(y[4] if y[0] == "inbound" else y[2])),
+ (ORD_DST_PORT, "Port (Dest.)",
+ lambda x, y: int(x[2] if x[0] == "inbound" else x[4]) - int(y[2] if y[0] == "inbound" else y[4]))]
+TYPE_COLORS = {"inbound": "green", "outbound": "blue", "control": "red"}
+TYPE_WEIGHTS = {"inbound": 0, "outbound": 1, "control": 2}
+
# provides bi-directional mapping of sorts with their associated labels
def getSortLabel(sortType, withColor = False):
"""
@@ -21,11 +40,11 @@
Connection Type red
IP * blue
Port * green
- Alphanumeric cyan
+ Bandwidth cyan
Country Code yellow
"""
- for (type, label) in SORT_TYPES:
+ for (type, label, func) in SORT_TYPES:
if sortType == type:
color = None
@@ -33,7 +52,7 @@
if label == "Connection Type": color = "red"
elif label.startswith("IP"): color = "blue"
elif label.startswith("Port"): color = "green"
- elif label == "Alphanumeric": color = "cyan"
+ elif label == "Bandwidth": color = "cyan"
elif label == "Country Code": color = "yellow"
if color: return "<%s>%s</%s>" % (color, label, color)
@@ -47,22 +66,18 @@
isn't recognized.
"""
- for (type, label) in SORT_TYPES:
+ for (type, label, func) in SORT_TYPES:
if sortLabel == label: return type
raise ValueError(sortLabel)
-# TODO: order by bandwidth
-# TODO: primary/secondary sort parameters
-
class ConnPanel(util.Panel):
"""
Lists netstat provided network data of tor.
"""
- def __init__(self, lock, conn):
+ def __init__(self, lock, conn, logger):
util.Panel.__init__(self, lock, -1)
self.scroll = 0
- logger = None
self.conn = conn # tor connection for querrying country codes
self.logger = logger # notified in case of problems
self.sortOrdering = [ORD_TYPE, ORD_SRC_IP, ORD_SRC_PORT]
@@ -70,25 +85,26 @@
# gets process id to make sure we get the correct netstat data
psCall = os.popen('ps -C tor -o pid')
try: self.pid = psCall.read().strip().split()[1]
- except IOError:
+ except Exception:
+ # ps call failed
self.logger.monitor_event("ERR", "Unable to resolve tor pid, abandoning connection listing")
- self.pid = -1 # ps call failed
+ self.pid = -1
psCall.close()
+ # uses ports to identify type of connections
self.orPort = self.conn.get_option("ORPort")[0][1]
self.dirPort = self.conn.get_option("DirPort")[0][1]
self.controlPort = self.conn.get_option("ControlPort")[0][1]
- # tuples of last netstat results with (source, destination)
- # addresses could be resolved and foreign locations followed by country code
- self.inboundConn = []
- self.outboundConn = []
- self.controlConn = []
-
- # alternative conn: (source IP, source port destination IP, destination port, country code, type)
+ # netstat results are tuples of the form:
+ # (type, local IP, local port, foreign IP, foreign port, country code)
self.connections = []
+ # count of total inbound, outbound, and control connections
+ self.connectionCount = [0, 0, 0]
+
# cache of DNS lookups, IP Address => hostname (None if couldn't be resolved)
+ # TODO: implement
self.hostnameResolution = {}
self.reset()
@@ -98,14 +114,10 @@
Reloads netstat results.
"""
- self.inboundConn = []
- self.outboundConn = []
- self.controlConn = []
-
self.connections = []
+ self.connectionCount = [0, 0, 0]
- # TODO: provide special message if there's no connections
- if self.pid == -1: return # TODO: how should this be handled?
+ if self.pid == -1: return # initilization had warned of failure - abandon
# looks at netstat for tor with stderr redirected to /dev/null, options are:
# n = prevents dns lookups, p = include process (say if it's tor), t = tcp only
@@ -119,25 +131,33 @@
local = param[3]
foreign = param[4]
- sourcePort = local[local.find(":") + 1:]
- if sourcePort == self.controlPort: self.controlConn.append((local, foreign))
+ localIP = local[:local.find(":")]
+ localPort = local[len(localIP) + 1:]
+ foreignIP = foreign[:foreign.find(":")]
+ foreignPort = foreign[len(foreignIP) + 1:]
+
+ if localPort in (self.orPort, self.dirPort):
+ type = "inbound"
+ self.connectionCount[0] += 1
+ elif localPort == self.controlPort:
+ type = "control"
+ self.connectionCount[2] += 1
else:
- # include country code for foreign address
- try:
- countryCodeCommand = "ip-to-country/%s" % foreign[:foreign.find(":")]
- countryCode = self.conn.get_info(countryCodeCommand)[countryCodeCommand]
- foreign = "%s (%s)" % (foreign, countryCode)
- except socket.error: pass
-
- if sourcePort == self.orPort or sourcePort == self.dirPort: self.inboundConn.append((foreign, local))
- else: self.outboundConn.append((local, foreign))
+ type = "outbound"
+ self.connectionCount[1] += 1
+
+ try:
+ countryCodeQuery = "ip-to-country/%s" % foreign[:foreign.find(":")]
+ countryCode = self.conn.get_info(countryCodeQuery)[countryCodeQuery]
+ except socket.error: countryCode = None
+
+ self.connections.append((type, localIP, localPort, foreignIP, foreignPort, countryCode))
except IOError:
- # TODO: provide warning of failure
- pass # netstat call failed
+ # netstat call failed
+ self.logger.monitor_event("WARN", "Unable to query netstat for new connections")
+
netstatCall.close()
-
- # sort by local ip address
- # TODO: implement
+ self.sortConnections()
def handleKey(self, key):
self._resetBounds()
@@ -153,23 +173,46 @@
if not self.lock.acquire(False): return
try:
self.clear()
- self.addstr(0, 0, "Connections (%i inbound, %i outbound, %i control):" % (len(self.inboundConn), len(self.outboundConn), len(self.controlConn)), util.LABEL_ATTR)
+ self.addstr(0, 0, "Connections (%i inbound, %i outbound, %i control):" % tuple(self.connectionCount), util.LABEL_ATTR)
- self.scroll = min(self.scroll, len(self.inboundConn) + len(self.outboundConn) + len(self.controlConn) - self.maxY + 1)
- skipEntries = self.scroll
- lineNum = 1
- connSets = [(self.inboundConn, "INBOUND", "green"),
- (self.outboundConn, "OUTBOUND", "blue"),
- (self.controlConn, "CONTROL", "red")]
+ self.scroll = max(min(self.scroll, len(self.connections) - self.maxY + 1), 0)
+ lineNum = (-1 * self.scroll) + 1
+ for entry in self.connections:
+ if lineNum >= 1:
+ type = entry[0]
+ color = TYPE_COLORS[type]
+ src = "%s:%s" % (entry[1], entry[2])
+ dst = "%s:%s %s" % (entry[3], entry[4], "" if type == "control" else "(%s)" % entry[5])
+ if type == "inbound": src, dst = dst, src
+ self.addfstr(lineNum, 0, "<%s>%-30s--> %-26s(<b>%s</b>)</%s>" % (color, src, dst, type.upper(), color))
+ lineNum += 1
- for connSet in connSets:
- for (source, dest) in connSet[0]:
- if skipEntries > 0:
- skipEntries = skipEntries - 1
- else:
- self.addfstr(lineNum, 0, "<%s>%-30s--> %-26s(<b>%s</b>)</%s>" % (connSet[2], source, dest, connSet[1], connSet[2]))
- lineNum = lineNum + 1
self.refresh()
finally:
self.lock.release()
+
+ def sortConnections(self):
+ """
+ Sorts connections according to currently set ordering. This takes into
+ account secondary and tertiary sub-keys in case of ties.
+ """
+
+ # Current implementation is very inefficient, but since connection lists
+ # are decently small (count get up to arounk 1k) this shouldn't be a big
+ # whoop. Suggestions for improvements are welcome!
+ self.connections.sort(lambda x, y: _multisort(x, y, self.sortOrdering))
+def _multisort(conn1, conn2, sorts):
+ # recursively checks primary, secondary, and tertiary sorting parameter in ties
+ comp = SORT_TYPES[sorts[0]][2](conn1, conn2)
+ if comp or len(sorts) == 1: return comp
+ else: return _multisort(conn1, conn2, sorts[1:])
+
+# provides comparison int for sorting IP addresses
+def _ipToInt(ipAddr):
+ total = 0
+ for comp in ipAddr.split("."):
+ total *= 255
+ total += int(comp)
+ return total
+
Modified: arm/trunk/interface/controller.py
===================================================================
--- arm/trunk/interface/controller.py 2009-07-09 02:17:47 UTC (rev 19956)
+++ arm/trunk/interface/controller.py 2009-07-09 05:31:36 UTC (rev 19957)
@@ -131,8 +131,8 @@
"popup": util.Panel(cursesLock, 9),
"bandwidth": bandwidthPanel.BandwidthMonitor(cursesLock, conn),
"log": logPanel.LogMonitor(cursesLock, loggedEvents),
- "conn": connPanel.ConnPanel(cursesLock, conn),
"torrc": confPanel.ConfPanel(cursesLock, conn.get_info("config-file")["config-file"])}
+ panels["conn"] = connPanel.ConnPanel(cursesLock, conn, panels["log"])
# listeners that update bandwidth and log panels with Tor status
conn.add_event_listener(panels["log"])
@@ -237,7 +237,7 @@
popup.addstr(1, 41, "down arrow: scroll down a line")
popup.addstr(2, 2, "page up: scroll up a page")
popup.addstr(2, 41, "page down: scroll down a page")
- #popup.addstr(3, 2, "s: sort ordering")
+ popup.addstr(3, 2, "s: sort ordering")
#popup.addstr(4, 2, "r: resolve hostnames")
#popup.addstr(4, 41, "R: hostname auto-resolution")
#popup.addstr(5, 2, "h: show IP/hostnames")
@@ -323,8 +323,6 @@
finally:
cursesLock.release()
elif page == 1 and (key == ord('s') or key == ord('S')):
- continue
-
# set ordering for connection listing
cursesLock.acquire()
try:
@@ -343,7 +341,7 @@
# Makes listing of all options
options = []
- for (type, label) in connPanel.SORT_TYPES: options.append(label)
+ for (type, label, func) in connPanel.SORT_TYPES: options.append(label)
options.append("Cancel")
while len(selections) < 3:
@@ -383,7 +381,9 @@
options.remove(selection)
cursorLoc = min(cursorLoc, len(options) - 1)
- if len(selections) == 3: panels["conn"].sortOrdering = selections
+ if len(selections) == 3:
+ panels["conn"].sortOrdering = selections
+ panels["conn"].sortConnections()
curses.halfdelay(REFRESH_RATE * 10) # reset normal pausing behavior
finally:
cursesLock.release()