def __init__(self, builder): CliConnectionPanel.__init__(self, None) self.builder = builder conn = torTools.getConn() torPid = conn.getMyPid() torCmdName = sysTools.getProcessName(torPid, 'tor') connections.getResolver(torCmdName, torPid, 'tor') treeStore = self.builder.get_object('treestore_conn') self._wrappedEntryLines = EntryLines(self._entryLines, treeStore)
def shutdownDaemons(): """ Stops and joins on worker threads. """ # prevents further worker threads from being spawned torTools.NO_SPAWN = True # stops panel daemons control = getController() for panelImpl in control.getDaemonPanels(): panelImpl.stop() for panelImpl in control.getDaemonPanels(): panelImpl.join() # joins on stem threads torTools.getConn().close() # joins on utility daemon threads - this might take a moment since the # internal threadpools being joined might be sleeping hostnames.stop() resourceTrackers = sysTools.RESOURCE_TRACKERS.values() resolver = connections.getResolver("tor") if connections.isResolverAlive("tor") else None for tracker in resourceTrackers: tracker.stop() if resolver: resolver.stop() # sets halt flag (returning immediately) for tracker in resourceTrackers: tracker.join() if resolver: resolver.join() # joins on halted resolver
def shutdownDaemons(): """ Stops and joins on worker threads. """ # prevents further worker threads from being spawned torTools.NO_SPAWN = True # stops panel daemons control = getController() for panelImpl in control.getDaemonPanels(): panelImpl.stop() for panelImpl in control.getDaemonPanels(): panelImpl.join() # joins on TorCtl event thread torTools.getConn().close() # joins on utility daemon threads - this might take a moment since the # internal threadpools being joined might be sleeping hostnames.stop() resourceTrackers = sysTools.RESOURCE_TRACKERS.values() resolver = connections.getResolver("tor") if connections.isResolverAlive( "tor") else None for tracker in resourceTrackers: tracker.stop() if resolver: resolver.stop() # sets halt flag (returning immediately) for tracker in resourceTrackers: tracker.join() if resolver: resolver.join() # joins on halted resolver
def handleKey(self, key): self.valsLock.acquire() isKeystrokeConsumed = True if uiTools.isScrollKey(key): pageHeight = self.getPreferredSize()[0] - 1 if self._showDetails: pageHeight -= (DETAILS_HEIGHT + 1) isChanged = self._scroller.handleKey(key, self._entryLines, pageHeight) if isChanged: self.redraw(True) elif uiTools.isSelectionKey(key): self._showDetails = not self._showDetails self.redraw(True) elif key == ord('s') or key == ord('S'): self.showSortDialog() elif key == ord('u') or key == ord('U'): # provides a menu to pick the connection resolver title = "Resolver Util:" options = ["auto"] + list(connections.Resolver) connResolver = connections.getResolver("tor") currentOverwrite = connResolver.overwriteResolver if currentOverwrite == None: oldSelection = 0 else: oldSelection = options.index(currentOverwrite) selection = cli.popups.showMenu(title, options, oldSelection) # applies new setting if selection != -1: selectedOption = options[selection] if selection != 0 else None connResolver.overwriteResolver = selectedOption elif key == ord('l') or key == ord('L'): # provides a menu to pick the primary information we list connections by title = "List By:" options = list(entries.ListingType) # dropping the HOSTNAME listing type until we support displaying that content options.remove(cli.connections.entries.ListingType.HOSTNAME) oldSelection = options.index(self.getListingType()) selection = cli.popups.showMenu(title, options, oldSelection) # applies new setting if selection != -1: self.setListingType(options[selection]) elif key == ord('d') or key == ord('D'): # presents popup for raw consensus data descriptorPopup.showDescriptorPopup(self) elif (key == ord('c') or key == ord('C')) and self.isClientsAllowed(): countPopup.showCountDialog(countPopup.CountType.CLIENT_LOCALE, self._clientLocaleUsage) elif (key == ord('e') or key == ord('E')) and self.isExitsAllowed(): countPopup.showCountDialog(countPopup.CountType.EXIT_PORT, self._exitPortUsage) else: isKeystrokeConsumed = False self.valsLock.release() return isKeystrokeConsumed
def handleKey(self, key): self.valsLock.acquire() isKeystrokeConsumed = True if uiTools.isScrollKey(key): pageHeight = self.getPreferredSize()[0] - 1 if self._showDetails: pageHeight -= (DETAILS_HEIGHT + 1) isChanged = self._scroller.handleKey(key, self._entryLines, pageHeight) if isChanged: self.redraw(True) elif uiTools.isSelectionKey(key): self._showDetails = not self._showDetails self.redraw(True) elif key == ord('s') or key == ord('S'): self.showSortDialog() elif key == ord('u') or key == ord('U'): # provides a menu to pick the connection resolver title = "Resolver Util:" options = ["auto"] + connections.Resolver.values() connResolver = connections.getResolver("tor") currentOverwrite = connResolver.overwriteResolver if currentOverwrite == None: oldSelection = 0 else: oldSelection = options.index(currentOverwrite) selection = cli.popups.showMenu(title, options, oldSelection) # applies new setting if selection != -1: selectedOption = options[selection] if selection != 0 else None connResolver.overwriteResolver = selectedOption elif key == ord('l') or key == ord('L'): # provides a menu to pick the primary information we list connections by title = "List By:" options = entries.ListingType.values() # dropping the HOSTNAME listing type until we support displaying that content options.remove(cli.connections.entries.ListingType.HOSTNAME) oldSelection = options.index(self._listingType) selection = cli.popups.showMenu(title, options, oldSelection) # applies new setting if selection != -1: self.setListingType(options[selection]) elif key == ord('d') or key == ord('D'): # presents popup for raw consensus data descriptorPopup.showDescriptorPopup(self) elif (key == ord('c') or key == ord('C')) and self.isClientsAllowed(): countPopup.showCountDialog(countPopup.CountType.CLIENT_LOCALE, self._clientLocaleUsage) elif (key == ord('e') or key == ord('E')) and self.isExitsAllowed(): countPopup.showCountDialog(countPopup.CountType.EXIT_PORT, self._exitPortUsage) else: isKeystrokeConsumed = False self.valsLock.release() return isKeystrokeConsumed
def eventTick(self): """ Fetches connection stats from cached information. """ inboundCount, outboundCount = 0, 0 for entry in connections.getResolver("tor").getConnections(): localPort = entry[1] if localPort in (self.orPort, self.dirPort): inboundCount += 1 elif localPort == self.controlPort: pass # control connection else: outboundCount += 1 self._processEvent(inboundCount, outboundCount)
def makeConnectionsMenu(connPanel): """ Submenu for the connections panel, consisting of... [X] IP Address [ ] Fingerprint [ ] Nickname Sorting... Resolver (Submenu) Arguments: connPanel - instance of the connections panel """ connectionsMenu = cli.menu.item.Submenu("Connections") # listing options listingGroup = cli.menu.item.SelectionGroup(connPanel.setListingType, connPanel.getListingType()) listingOptions = list(cli.connections.entries.ListingType) listingOptions.remove(cli.connections.entries.ListingType.HOSTNAME) for option in listingOptions: connectionsMenu.add( cli.menu.item.SelectionMenuItem(option, listingGroup, option)) # sorting option connectionsMenu.add( cli.menu.item.MenuItem("Sorting...", connPanel.showSortDialog)) # resolver submenu connResolver = connections.getResolver("tor") resolverMenu = cli.menu.item.Submenu("Resolver") resolverGroup = cli.menu.item.SelectionGroup( connResolver.setOverwriteResolver, connResolver.getOverwriteResolver()) resolverMenu.add( cli.menu.item.SelectionMenuItem("auto", resolverGroup, None)) for option in connections.Resolver: resolverMenu.add( cli.menu.item.SelectionMenuItem(option, resolverGroup, option)) connectionsMenu.add(resolverMenu) return connectionsMenu
def makeConnectionsMenu(connPanel): """ Submenu for the connections panel, consisting of... [X] IP Address [ ] Fingerprint [ ] Nickname Sorting... Resolver (Submenu) Arguments: connPanel - instance of the connections panel """ connectionsMenu = cli.menu.item.Submenu("Connections") # listing options listingGroup = cli.menu.item.SelectionGroup(connPanel.setListingType, connPanel.getListingType()) listingOptions = list(cli.connections.entries.ListingType) listingOptions.remove(cli.connections.entries.ListingType.HOSTNAME) for option in listingOptions: connectionsMenu.add(cli.menu.item.SelectionMenuItem(option, listingGroup, option)) # sorting option connectionsMenu.add(cli.menu.item.MenuItem("Sorting...", connPanel.showSortDialog)) # resolver submenu connResolver = connections.getResolver("tor") resolverMenu = cli.menu.item.Submenu("Resolver") resolverGroup = cli.menu.item.SelectionGroup(connResolver.setOverwriteResolver, connResolver.getOverwriteResolver()) resolverMenu.add(cli.menu.item.SelectionMenuItem("auto", resolverGroup, None)) for option in connections.Resolver: resolverMenu.add(cli.menu.item.SelectionMenuItem(option, resolverGroup, option)) connectionsMenu.add(resolverMenu) return connectionsMenu
def getHelp(self): resolverUtil = connections.getResolver("tor").overwriteResolver if resolverUtil == None: resolverUtil = "auto" options = [] options.append(("up arrow", "scroll up a line", None)) options.append(("down arrow", "scroll down a line", None)) options.append(("page up", "scroll up a page", None)) options.append(("page down", "scroll down a page", None)) options.append(("enter", "show connection details", None)) options.append(("d", "raw consensus descriptor", None)) if self.isClientsAllowed(): options.append(("c", "client locale usage summary", None)) if self.isExitsAllowed(): options.append(("e", "exit port usage summary", None)) options.append(("l", "listed identity", self.getListingType().lower())) options.append(("s", "sort ordering", None)) options.append(("u", "resolving utility", resolverUtil)) return options
def getHelp(self): resolverUtil = connections.getResolver("tor").overwriteResolver if resolverUtil == None: resolverUtil = "auto" options = [] options.append(("up arrow", "scroll up a line", None)) options.append(("down arrow", "scroll down a line", None)) options.append(("page up", "scroll up a page", None)) options.append(("page down", "scroll down a page", None)) options.append(("enter", "edit configuration option", None)) options.append(("d", "raw consensus descriptor", None)) if self.isClientsAllowed(): options.append(("c", "client locale usage summary", None)) if self.isExitsAllowed(): options.append(("e", "exit port usage summary", None)) options.append(("l", "listed identity", self._listingType.lower())) options.append(("s", "sort ordering", None)) options.append(("u", "resolving utility", resolverUtil)) return options
def connResetListener(controller, eventType, _): """ Pauses connection resolution when tor's shut down, and resumes with the new pid if started again. """ if connections.isResolverAlive("tor"): resolver = connections.getResolver("tor") resolver.setPaused(eventType == State.CLOSED) if eventType in (State.INIT, State.RESET): # Reload the torrc contents. If the torrc panel is present then it will # do this instead since it wants to do validation and redraw _after_ the # new contents are loaded. if getController().getPanel("torrc") == None: torConfig.getTorrc().load(True) torPid = controller.get_info("process/pid", None) if torPid and torPid != resolver.getPid(): resolver.setPid(torPid)
def connResetListener(conn, eventType): """ Pauses connection resolution when tor's shut down, and resumes with the new pid if started again. """ if connections.isResolverAlive("tor"): resolver = connections.getResolver("tor") resolver.setPaused(eventType == torTools.State.CLOSED) if eventType in (torTools.State.INIT, torTools.State.RESET): # Reload the torrc contents. If the torrc panel is present then it will # do this instead since it wants to do validation and redraw _after_ the # new contents are loaded. if getController().getPanel("torrc") == None: torConfig.getTorrc().load(True) torPid = conn.getMyPid() if torPid and torPid != resolver.getPid(): resolver.setPid(torPid)
def reset(self): """ Reloads connection results. """ if self.isDisabled: return # inaccessable during startup so might need to be refetched try: if not self.address: self.address = self.conn.get_info("address")["address"] except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed): pass self.connectionsLock.acquire() self.clientConnectionLock.acquire() # temporary variables for connections and count connectionsTmp = [] connectionCountTmp = [0] * 5 familyResolutionsTmp = {} # used (with isBridge) to determine if inbound connections should be scrubbed isGuard = False try: myFingerprint = self.conn.get_info("fingerprint") nsCall = self.conn.get_network_status("id/%s" % myFingerprint) if nsCall: isGuard = "Guard" in nsCall[0].flags else: raise TorCtl.ErrorReply # network consensus couldn't be fetched except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed): pass try: if self.clientConnectionCache == None: # client connection cache was invalidated self.clientConnectionCache = _getClientConnections(self.conn) connTimes = {} # mapping of ip/port to connection time for entry in (self.connections if not self.isPaused else self.connectionsBuffer): connTimes[(entry[CONN_F_IP], entry[CONN_F_PORT])] = entry[CONN_TIME] results = connections.getResolver("tor").getConnections() if results == self.lastConnResults: return # contents haven't changed for lIp, lPort, fIp, fPort in results: fingerprint = self.getFingerprint(fIp, fPort) isPrivate = False if lPort in (self.listenPort, self.dirPort): type = "inbound" connectionCountTmp[0] += 1 if SCRUB_PRIVATE_DATA and fIp not in self.fingerprintMappings.keys(): isPrivate = isGuard or self.isBridge elif lPort == self.socksPort: type = "client" connectionCountTmp[2] += 1 elif lPort == self.controlPort: type = "control" connectionCountTmp[4] += 1 else: nickname = self.getNickname(fIp, fPort) isClient = False for clientName in self.clientConnectionCache: if nickname == clientName or (len(clientName) > 1 and clientName[0] == "$" and fingerprint == clientName[1:]): isClient = True break if isClient: type = "client" connectionCountTmp[2] += 1 elif (fIp, fPort) in DIR_SERVERS: type = "directory" connectionCountTmp[3] += 1 else: type = "outbound" connectionCountTmp[1] += 1 if SCRUB_PRIVATE_DATA and fIp not in self.fingerprintMappings.keys(): isPrivate = isExitAllowed(fIp, fPort, self.exitPolicy, self.exitRejectPrivate) # replace nat address with external version if available and the # external address isn't a private IP # TODO: range should restrict to the following address ranges: # 10.*, 172.16.* - 172.31.*, 192.168.* # being lazy right now - fix the 172.* range when rewriting isPrivateIp = fIp.startswith("10.") or fIp.startswith("192.168.") or fIp.startswith("172.") if self.address and type != "control" and not isPrivateIp: lIp = self.address try: countryCodeQuery = "ip-to-country/%s" % fIp countryCode = self.conn.get_info(countryCodeQuery)[countryCodeQuery] except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed): countryCode = "??" if not self.providedGeoipWarning: log.log(log.WARN, "Tor geoip database is unavailable.") self.providedGeoipWarning = True if (fIp, fPort) in connTimes: connTime = connTimes[(fIp, fPort)] else: connTime = time.time() connectionsTmp.append((type, lIp, lPort, fIp, fPort, countryCode, connTime, isPrivate)) # appends localhost connection to allow user to look up their own consensus entry selfFingerprint = None try: selfFingerprint = self.conn.get_info("fingerprint")["fingerprint"] except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed): pass if self.address and selfFingerprint: try: countryCodeQuery = "ip-to-country/%s" % self.address selfCountryCode = self.conn.get_info(countryCodeQuery)[countryCodeQuery] except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed): selfCountryCode = "??" if (self.address, self.orPort) in connTimes: connTime = connTimes[(self.address, self.orPort)] else: connTime = time.time() self.localhostEntry = (("localhost", self.address, self.orPort, self.address, self.orPort, selfCountryCode, connTime, False), selfFingerprint) connectionsTmp.append(self.localhostEntry[0]) else: self.localhostEntry = None # appends family connections tmpCounter = 0 # used for unique port of unresolved family entries (funky hack) for familyEntry in self.family: # TODO: turns out that "ns/name/<OR nickname>" accpets fingerprint # identifiers, so all this nickname -> fingerprint work is unnecessary, # but used for fingerprint lookup performance in draw... this could be # improved (might be completely unnecessary due to the fingerprint # lookup cache) fingerprint = None if familyEntry in self.familyFingerprints: fingerprint = self.familyFingerprints[familyEntry] try: if fingerprint: nsCall = self.conn.get_network_status("id/%s" % fingerprint) else: nsCall = self.conn.get_network_status("name/%s" % familyEntry) if nsCall: familyAddress, familyPort = nsCall[0].ip, nsCall[0].orport else: raise TorCtl.ErrorReply # network consensus couldn't be fetched countryCodeQuery = "ip-to-country/%s" % familyAddress familyCountryCode = self.conn.get_info(countryCodeQuery)[countryCodeQuery] if (familyAddress, familyPort) in connTimes: connTime = connTimes[(familyAddress, familyPort)] else: connTime = time.time() if fingerprint: familyResolutionsTmp[(familyAddress, familyPort)] = fingerprint connectionsTmp.append(("family", familyAddress, familyPort, familyAddress, familyPort, familyCountryCode, connTime, False)) except (socket.error, TorCtl.ErrorReply): # use dummy entry for sorting - the draw function notes that entries are unknown portIdentifier = str(65536 + tmpCounter) if fingerprint: familyResolutionsTmp[("256.255.255.255", portIdentifier)] = fingerprint connectionsTmp.append(("family", "256.255.255.255", portIdentifier, "256.255.255.255", portIdentifier, "??", time.time(), False)) tmpCounter += 1 except TorCtl.TorCtlClosed: pass # connections aren't shown when control port is unavailable self.lastUpdate = time.time() # assigns results if self.isPaused: self.connectionsBuffer = connectionsTmp self.connectionCountBuffer = connectionCountTmp self.familyResolutionsBuffer = familyResolutionsTmp else: self.connections = connectionsTmp self.connectionCount = connectionCountTmp self.familyResolutions = familyResolutionsTmp # hostnames are sorted at draw - otherwise now's a good time if self.listingType != LIST_HOSTNAME: self.sortConnections() self.lastConnResults = results finally: self.connectionsLock.release() self.clientConnectionLock.release()
def startTorMonitor(startTime): """ Initializes the interface and starts the main draw loop. Arguments: startTime - unix time for when arm was started """ # initializes interface configs config = conf.getConfig("arm") config.update(CONFIG, { "features.redrawRate": 1, "features.refreshRate": 0 }) cli.graphing.graphPanel.loadConfig(config) cli.connections.connEntry.loadConfig(config) cli.wizard.loadConfig(config) # attempts to fetch the tor pid, warning if unsuccessful (this is needed for # checking its resource usage, among other things) conn = torTools.getConn() torPid = conn.getMyPid() if not torPid and conn.isAlive(): msg = "Unable to determine Tor's pid. Some information, like its resource usage will be unavailable." log.log(CONFIG["log.unknownTorPid"], msg) # adds events needed for arm functionality to the torTools REQ_EVENTS # mapping (they're then included with any setControllerEvents call, and log # a more helpful error if unavailable) torTools.REQ_EVENTS["BW"] = "bandwidth graph won't function" if not CONFIG["startup.blindModeEnabled"]: torTools.REQ_EVENTS[ "CIRC"] = "may cause issues in identifying client connections" # Configures connection resoultions. This is paused/unpaused according to # if Tor's connected or not. conn.addStatusListener(connResetListener) if torPid: # use the tor pid to help narrow connection results torCmdName = sysTools.getProcessName(torPid, "tor") connections.getResolver(torCmdName, torPid, "tor") else: # constructs singleton resolver and, if tor isn't connected, initizes # it to be paused connections.getResolver("tor").setPaused(not conn.isAlive()) # hack to display a better (arm specific) notice if all resolvers fail connections.RESOLVER_FINAL_FAILURE_MSG = "We were unable to use any of your system's resolvers to get tor's connections. This is fine, but means that the connections page will be empty. This is usually permissions related so if you would like to fix this then run arm with the same user as tor (ie, \"sudo -u <tor user> arm\")." # provides a notice about any event types tor supports but arm doesn't missingEventTypes = cli.logPanel.getMissingEventTypes() if missingEventTypes: pluralLabel = "s" if len(missingEventTypes) > 1 else "" log.log( CONFIG["log.torEventTypeUnrecognized"], "arm doesn't recognize the following event type%s: %s (log 'UNKNOWN' events to see them)" % (pluralLabel, ", ".join(missingEventTypes))) try: curses.wrapper(drawTorMonitor, startTime) except KeyboardInterrupt: # Skip printing stack trace in case of keyboard interrupt. The # HALT_ACTIVITY attempts to prevent daemons from triggering a curses redraw # (which would leave the user's terminal in a screwed up state). There is # still a tiny timing issue here (after the exception but before the flag # is set) but I've never seen it happen in practice. panel.HALT_ACTIVITY = True shutdownDaemons()
def _update(self): """ Fetches the newest resolved connections. """ self.appResolveSinceUpdate = False # if we don't have an initialized resolver then this is a no-op if not connections.isResolverAlive("tor"): return connResolver = connections.getResolver("tor") currentResolutionCount = connResolver.getResolutionCount() self.valsLock.acquire() newEntries = [] # the new results we'll display # Fetches new connections and client circuits... # newConnections [(local ip, local port, foreign ip, foreign port)...] # newCircuits {circuitID => (status, purpose, path)...} newConnections = connResolver.getConnections() newCircuits = {} for circuitID, status, purpose, path in torTools.getConn().getCircuits( ): # Skips established single-hop circuits (these are for directory # fetches, not client circuits) if not (status == "BUILT" and len(path) == 1): newCircuits[circuitID] = (status, purpose, path) # Populates newEntries with any of our old entries that still exist. # This is both for performance and to keep from resetting the uptime # attributes. Note that CircEntries are a ConnectionEntry subclass so # we need to check for them first. for oldEntry in self._entries: if isinstance(oldEntry, circEntry.CircEntry): newEntry = newCircuits.get(oldEntry.circuitID) if newEntry: oldEntry.update(newEntry[0], newEntry[2]) newEntries.append(oldEntry) del newCircuits[oldEntry.circuitID] elif isinstance(oldEntry, connEntry.ConnectionEntry): connLine = oldEntry.getLines()[0] connAttr = (connLine.local.getIpAddr(), connLine.local.getPort(), connLine.foreign.getIpAddr(), connLine.foreign.getPort()) if connAttr in newConnections: newEntries.append(oldEntry) newConnections.remove(connAttr) # Reset any display attributes for the entries we're keeping for entry in newEntries: entry.resetDisplay() # Adds any new connection and circuit entries. for lIp, lPort, fIp, fPort in newConnections: newConnEntry = connEntry.ConnectionEntry(lIp, lPort, fIp, fPort) newConnLine = newConnEntry.getLines()[0] if newConnLine.getType() != connEntry.Category.CIRCUIT: newEntries.append(newConnEntry) # updates exit port and client locale usage information if newConnLine.isPrivate(): if newConnLine.getType() == connEntry.Category.INBOUND: # client connection, update locale information clientLocale = newConnLine.foreign.getLocale() if clientLocale: self._clientLocaleUsage[ clientLocale] = self._clientLocaleUsage.get( clientLocale, 0) + 1 elif newConnLine.getType() == connEntry.Category.EXIT: exitPort = newConnLine.foreign.getPort() self._exitPortUsage[ exitPort] = self._exitPortUsage.get(exitPort, 0) + 1 for circuitID in newCircuits: status, purpose, path = newCircuits[circuitID] newEntries.append( circEntry.CircEntry(circuitID, status, purpose, path)) # Counts the relays in each of the categories. This also flushes the # type cache for all of the connections (in case its changed since last # fetched). categoryTypes = list(connEntry.Category) typeCounts = dict((type, 0) for type in categoryTypes) for entry in newEntries: if isinstance(entry, connEntry.ConnectionEntry): typeCounts[entry.getLines()[0].getType()] += 1 elif isinstance(entry, circEntry.CircEntry): typeCounts[connEntry.Category.CIRCUIT] += 1 # makes labels for all the categories with connections (ie, # "21 outbound", "1 control", etc) countLabels = [] for category in categoryTypes: if typeCounts[category] > 0: countLabels.append("%i %s" % (typeCounts[category], category.lower())) if countLabels: self._title = "Connections (%s):" % ", ".join(countLabels) else: self._title = "Connections:" self._entries = newEntries self._entryLines = [] for entry in self._entries: self._entryLines += entry.getLines() self.setSortOrder() self._lastResourceFetch = currentResolutionCount self.valsLock.release()
def startTorMonitor(startTime): """ Initializes the interface and starts the main draw loop. Arguments: startTime - unix time for when arm was started """ # initializes interface configs config = conf.getConfig("arm") config.update(CONFIG, { "features.redrawRate": 1, "features.refreshRate": 0}) cli.graphing.graphPanel.loadConfig(config) cli.connections.connEntry.loadConfig(config) cli.wizard.loadConfig(config) # attempts to fetch the tor pid, warning if unsuccessful (this is needed for # checking its resource usage, among other things) conn = torTools.getConn() torPid = conn.getMyPid() if not torPid and conn.isAlive(): msg = "Unable to determine Tor's pid. Some information, like its resource usage will be unavailable." log.log(CONFIG["log.unknownTorPid"], msg) # adds events needed for arm functionality to the torTools REQ_EVENTS # mapping (they're then included with any setControllerEvents call, and log # a more helpful error if unavailable) torTools.REQ_EVENTS["BW"] = "bandwidth graph won't function" if not CONFIG["startup.blindModeEnabled"]: torTools.REQ_EVENTS["CIRC"] = "may cause issues in identifying client connections" # Configures connection resoultions. This is paused/unpaused according to # if Tor's connected or not. conn.addStatusListener(connResetListener) if torPid: # use the tor pid to help narrow connection results torCmdName = sysTools.getProcessName(torPid, "tor") connections.getResolver(torCmdName, torPid, "tor") else: # constructs singleton resolver and, if tor isn't connected, initizes # it to be paused connections.getResolver("tor").setPaused(not conn.isAlive()) # hack to display a better (arm specific) notice if all resolvers fail connections.RESOLVER_FINAL_FAILURE_MSG = "We were unable to use any of your system's resolvers to get tor's connections. This is fine, but means that the connections page will be empty. This is usually permissions related so if you would like to fix this then run arm with the same user as tor (ie, \"sudo -u <tor user> arm\")." # provides a notice about any event types tor supports but arm doesn't missingEventTypes = cli.logPanel.getMissingEventTypes() if missingEventTypes: pluralLabel = "s" if len(missingEventTypes) > 1 else "" log.log(CONFIG["log.torEventTypeUnrecognized"], "arm doesn't recognize the following event type%s: %s (log 'UNKNOWN' events to see them)" % (pluralLabel, ", ".join(missingEventTypes))) try: curses.wrapper(drawTorMonitor, startTime) except KeyboardInterrupt: # Skip printing stack trace in case of keyboard interrupt. The # HALT_ACTIVITY attempts to prevent daemons from triggering a curses redraw # (which would leave the user's terminal in a screwed up state). There is # still a tiny timing issue here (after the exception but before the flag # is set) but I've never seen it happen in practice. panel.HALT_ACTIVITY = True shutdownDaemons()
def startTorMonitor(startTime): """ Initializes the interface and starts the main draw loop. Arguments: startTime - unix time for when arm was started """ # attempts to fetch the tor pid, warning if unsuccessful (this is needed for # checking its resource usage, among other things) conn = torTools.getConn() torPid = conn.getMyPid() if not torPid and conn.isAlive(): log.warn("Unable to determine Tor's pid. Some information, like its resource usage will be unavailable.") # adds events needed for arm functionality to the torTools REQ_EVENTS # mapping (they're then included with any setControllerEvents call, and log # a more helpful error if unavailable) torTools.REQ_EVENTS["BW"] = "bandwidth graph won't function" if not CONFIG["startup.blindModeEnabled"]: # The DisableDebuggerAttachment will prevent our connection panel from really # functioning. It'll have circuits, but little else. If this is the case then # notify the user and tell them what they can do to fix it. if conn.getOption("DisableDebuggerAttachment", None) == "1": log.notice( "Tor is preventing system utilities like netstat and lsof from working. This means that arm can't provide you with connection information. You can change this by adding 'DisableDebuggerAttachment 0' to your torrc and restarting tor. For more information see...\nhttps://trac.torproject.org/3313" ) connections.getResolver("tor").setPaused(True) else: torTools.REQ_EVENTS["CIRC"] = "may cause issues in identifying client connections" # Configures connection resoultions. This is paused/unpaused according to # if Tor's connected or not. conn.addStatusListener(connResetListener) if torPid: # use the tor pid to help narrow connection results torCmdName = sysTools.getProcessName(torPid, "tor") connections.getResolver(torCmdName, torPid, "tor") else: # constructs singleton resolver and, if tor isn't connected, initizes # it to be paused connections.getResolver("tor").setPaused(not conn.isAlive()) # hack to display a better (arm specific) notice if all resolvers fail connections.RESOLVER_FINAL_FAILURE_MSG = "We were unable to use any of your system's resolvers to get tor's connections. This is fine, but means that the connections page will be empty. This is usually permissions related so if you would like to fix this then run arm with the same user as tor (ie, \"sudo -u <tor user> arm\")." # provides a notice about any event types tor supports but arm doesn't missingEventTypes = cli.logPanel.getMissingEventTypes() if missingEventTypes: pluralLabel = "s" if len(missingEventTypes) > 1 else "" log.info( "arm doesn't recognize the following event type%s: %s (log 'UNKNOWN' events to see them)" % (pluralLabel, ", ".join(missingEventTypes)) ) try: curses.wrapper(drawTorMonitor, startTime) except KeyboardInterrupt: # Skip printing stack trace in case of keyboard interrupt. The # HALT_ACTIVITY attempts to prevent daemons from triggering a curses redraw # (which would leave the user's terminal in a screwed up state). There is # still a tiny timing issue here (after the exception but before the flag # is set) but I've never seen it happen in practice. panel.HALT_ACTIVITY = True shutdownDaemons()
def startTorMonitor(startTime): """ Initializes the interface and starts the main draw loop. Arguments: startTime - unix time for when arm was started """ # attempts to fetch the tor pid, warning if unsuccessful (this is needed for # checking its resource usage, among other things) conn = torTools.getConn() torPid = conn.getMyPid() if not torPid and conn.isAlive(): log.warn( "Unable to determine Tor's pid. Some information, like its resource usage will be unavailable." ) # adds events needed for arm functionality to the torTools REQ_EVENTS # mapping (they're then included with any setControllerEvents call, and log # a more helpful error if unavailable) torTools.REQ_EVENTS["BW"] = "bandwidth graph won't function" if not CONFIG["startup.blindModeEnabled"]: # The DisableDebuggerAttachment will prevent our connection panel from really # functioning. It'll have circuits, but little else. If this is the case then # notify the user and tell them what they can do to fix it. if conn.getOption("DisableDebuggerAttachment", None) == "1": log.notice( "Tor is preventing system utilities like netstat and lsof from working. This means that arm can't provide you with connection information. You can change this by adding 'DisableDebuggerAttachment 0' to your torrc and restarting tor. For more information see...\nhttps://trac.torproject.org/3313" ) connections.getResolver("tor").setPaused(True) else: torTools.REQ_EVENTS[ "CIRC"] = "may cause issues in identifying client connections" # Configures connection resoultions. This is paused/unpaused according to # if Tor's connected or not. conn.addStatusListener(connResetListener) if torPid: # use the tor pid to help narrow connection results torCmdName = sysTools.getProcessName(torPid, "tor") connections.getResolver(torCmdName, torPid, "tor") else: # constructs singleton resolver and, if tor isn't connected, initizes # it to be paused connections.getResolver("tor").setPaused(not conn.isAlive()) # hack to display a better (arm specific) notice if all resolvers fail connections.RESOLVER_FINAL_FAILURE_MSG = "We were unable to use any of your system's resolvers to get tor's connections. This is fine, but means that the connections page will be empty. This is usually permissions related so if you would like to fix this then run arm with the same user as tor (ie, \"sudo -u <tor user> arm\")." # provides a notice about any event types tor supports but arm doesn't missingEventTypes = cli.logPanel.getMissingEventTypes() if missingEventTypes: pluralLabel = "s" if len(missingEventTypes) > 1 else "" log.info( "arm doesn't recognize the following event type%s: %s (log 'UNKNOWN' events to see them)" % (pluralLabel, ", ".join(missingEventTypes))) try: curses.wrapper(drawTorMonitor, startTime) except KeyboardInterrupt: # Skip printing stack trace in case of keyboard interrupt. The # HALT_ACTIVITY attempts to prevent daemons from triggering a curses redraw # (which would leave the user's terminal in a screwed up state). There is # still a tiny timing issue here (after the exception but before the flag # is set) but I've never seen it happen in practice. panel.HALT_ACTIVITY = True shutdownDaemons()
def _update(self): """ Fetches the newest resolved connections. """ self.appResolveSinceUpdate = False # if we don't have an initialized resolver then this is a no-op if not connections.isResolverAlive("tor"): return connResolver = connections.getResolver("tor") currentResolutionCount = connResolver.getResolutionCount() if self._lastResourceFetch != currentResolutionCount: self.valsLock.acquire() newEntries = [] # the new results we'll display # Fetches new connections and client circuits... # newConnections [(local ip, local port, foreign ip, foreign port)...] # newCircuits {circuitID => (status, purpose, path)...} newConnections = connResolver.getConnections() newCircuits = {} for circuitID, status, purpose, path in torTools.getConn().getCircuits(): # Skips established single-hop circuits (these are for directory # fetches, not client circuits) if not (status == "BUILT" and len(path) == 1): newCircuits[circuitID] = (status, purpose, path) # Populates newEntries with any of our old entries that still exist. # This is both for performance and to keep from resetting the uptime # attributes. Note that CircEntries are a ConnectionEntry subclass so # we need to check for them first. for oldEntry in self._entries: if isinstance(oldEntry, circEntry.CircEntry): newEntry = newCircuits.get(oldEntry.circuitID) if newEntry: oldEntry.update(newEntry[0], newEntry[2]) newEntries.append(oldEntry) del newCircuits[oldEntry.circuitID] elif isinstance(oldEntry, connEntry.ConnectionEntry): connLine = oldEntry.getLines()[0] connAttr = (connLine.local.getIpAddr(), connLine.local.getPort(), connLine.foreign.getIpAddr(), connLine.foreign.getPort()) if connAttr in newConnections: newEntries.append(oldEntry) newConnections.remove(connAttr) # Reset any display attributes for the entries we're keeping for entry in newEntries: entry.resetDisplay() # Adds any new connection and circuit entries. for lIp, lPort, fIp, fPort in newConnections: newConnEntry = connEntry.ConnectionEntry(lIp, lPort, fIp, fPort) newConnLine = newConnEntry.getLines()[0] if newConnLine.getType() != connEntry.Category.CIRCUIT: newEntries.append(newConnEntry) # updates exit port and client locale usage information if newConnLine.isPrivate(): if newConnLine.getType() == connEntry.Category.INBOUND: # client connection, update locale information clientLocale = newConnLine.foreign.getLocale() if clientLocale: self._clientLocaleUsage[clientLocale] = self._clientLocaleUsage.get(clientLocale, 0) + 1 elif newConnLine.getType() == connEntry.Category.EXIT: exitPort = newConnLine.foreign.getPort() self._exitPortUsage[exitPort] = self._exitPortUsage.get(exitPort, 0) + 1 for circuitID in newCircuits: status, purpose, path = newCircuits[circuitID] newEntries.append(circEntry.CircEntry(circuitID, status, purpose, path)) # Counts the relays in each of the categories. This also flushes the # type cache for all of the connections (in case its changed since last # fetched). categoryTypes = connEntry.Category.values() typeCounts = dict((type, 0) for type in categoryTypes) for entry in newEntries: if isinstance(entry, connEntry.ConnectionEntry): typeCounts[entry.getLines()[0].getType()] += 1 elif isinstance(entry, circEntry.CircEntry): typeCounts[connEntry.Category.CIRCUIT] += 1 # makes labels for all the categories with connections (ie, # "21 outbound", "1 control", etc) countLabels = [] for category in categoryTypes: if typeCounts[category] > 0: countLabels.append("%i %s" % (typeCounts[category], category.lower())) if countLabels: self._title = "Connections (%s):" % ", ".join(countLabels) else: self._title = "Connections:" self._entries = newEntries self._entryLines = [] for entry in self._entries: self._entryLines += entry.getLines() self.setSortOrder() self._lastResourceFetch = currentResolutionCount self.valsLock.release()