[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()