/usr/share/arm/util/connections.py is in tor-arm 1.4.5.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 | """
Fetches connection data (IP addresses and ports) associated with a given
process. This sort of data can be retrieved via a variety of common *nix
utilities:
- netstat netstat -np | grep "ESTABLISHED <pid>/<process>"
- sockstat sockstat | egrep "<process> *<pid>.*ESTABLISHED"
- lsof lsof -wnPi | egrep "^<process> *<pid>.*((UDP.*)|(\(ESTABLISHED\)))"
- ss ss -nptu | grep "ESTAB.*\"<process>\",<pid>"
all queries dump its stderr (directing it to /dev/null). Results include UDP
and established TCP connections.
FreeBSD lacks support for the needed netstat flags and has a completely
different program for 'ss'. However, lsof works and there's a couple other
options that perform even better (thanks to Fabian Keil and Hans Schnehl):
- sockstat sockstat -4c | grep '<process> *<pid>'
- procstat procstat -f <pid> | grep TCP | grep -v 0.0.0.0:0
"""
import os
import time
import threading
from util import enum, log, procTools, sysTools
# enums for connection resolution utilities
Resolver = enum.Enum(("PROC", "proc"),
("NETSTAT", "netstat"),
("SS", "ss"),
("LSOF", "lsof"),
("SOCKSTAT", "sockstat"),
("BSD_SOCKSTAT", "sockstat (bsd)"),
("BSD_PROCSTAT", "procstat (bsd)"))
# If true this provides new instantiations for resolvers if the old one has
# been stopped. This can make it difficult ensure all threads are terminated
# when accessed concurrently.
RECREATE_HALTED_RESOLVERS = False
# formatted strings for the commands to be executed with the various resolvers
# options are:
# n = prevents dns lookups, p = include process
# output:
# tcp 0 0 127.0.0.1:9051 127.0.0.1:53308 ESTABLISHED 9912/tor
# *note: bsd uses a different variant ('-t' => '-p tcp', but worse an
# equivilant -p doesn't exist so this can't function)
RUN_NETSTAT = "netstat -np | grep \"ESTABLISHED %s/%s\""
# n = numeric ports, p = include process, t = tcp sockets, u = udp sockets
# output:
# ESTAB 0 0 127.0.0.1:9051 127.0.0.1:53308 users:(("tor",9912,20))
# *note: under freebsd this command belongs to a spreadsheet program
RUN_SS = "ss -nptu | grep \"ESTAB.*\\\"%s\\\",%s\""
# n = prevent dns lookups, P = show port numbers (not names), i = ip only,
# -w = no warnings
# output:
# tor 3873 atagar 45u IPv4 40994 0t0 TCP 10.243.55.20:45724->194.154.227.109:9001 (ESTABLISHED)
#
# oddly, using the -p flag via:
# lsof lsof -nPi -p <pid> | grep "^<process>.*(ESTABLISHED)"
# is much slower (11-28% in tests I ran)
RUN_LSOF = "lsof -wnPi | egrep \"^%s *%s.*((UDP.*)|(\\(ESTABLISHED\\)))\""
# output:
# atagar tor 3475 tcp4 127.0.0.1:9051 127.0.0.1:38942 ESTABLISHED
# *note: this isn't available by default under ubuntu
RUN_SOCKSTAT = "sockstat | egrep \"%s *%s.*ESTABLISHED\""
RUN_BSD_SOCKSTAT = "sockstat -4c | grep '%s *%s'"
RUN_BSD_PROCSTAT = "procstat -f %s | grep TCP | grep -v 0.0.0.0:0"
RESOLVERS = [] # connection resolvers available via the singleton constructor
RESOLVER_FAILURE_TOLERANCE = 3 # number of subsequent failures before moving on to another resolver
RESOLVER_SERIAL_FAILURE_MSG = "Unable to query connections with %s, trying %s"
RESOLVER_FINAL_FAILURE_MSG = "All connection resolvers failed"
CONFIG = {"queries.connections.minRate": 5,
"log.connResolverOptions": log.INFO,
"log.connLookupFailed": log.INFO,
"log.connLookupFailover": log.NOTICE,
"log.connLookupAbandon": log.NOTICE,
"log.connLookupRateGrowing": None,
"log.configEntryTypeError": log.NOTICE}
PORT_USAGE = {}
def loadConfig(config):
config.update(CONFIG)
for configKey in config.getKeys():
# fetches any port.label.* values
if configKey.startswith("port.label."):
portEntry = configKey[11:]
purpose = config.get(configKey)
divIndex = portEntry.find("-")
if divIndex == -1:
# single port
if portEntry.isdigit():
PORT_USAGE[portEntry] = purpose
else:
msg = "Port value isn't numeric for entry: %s" % configKey
log.log(CONFIG["log.configEntryTypeError"], msg)
else:
try:
# range of ports (inclusive)
minPort = int(portEntry[:divIndex])
maxPort = int(portEntry[divIndex + 1:])
if minPort > maxPort: raise ValueError()
for port in range(minPort, maxPort + 1):
PORT_USAGE[str(port)] = purpose
except ValueError:
msg = "Unable to parse port range for entry: %s" % configKey
log.log(CONFIG["log.configEntryTypeError"], msg)
def isValidIpAddress(ipStr):
"""
Returns true if input is a valid IPv4 address, false otherwise.
"""
# checks if theres four period separated values
if not ipStr.count(".") == 3: return False
# checks that each value in the octet are decimal values between 0-255
for ipComp in ipStr.split("."):
if not ipComp.isdigit() or int(ipComp) < 0 or int(ipComp) > 255:
return False
return True
def isIpAddressPrivate(ipAddr):
"""
Provides true if the IP address belongs on the local network or belongs to
loopback, false otherwise. These include:
Private ranges: 10.*, 172.16.* - 172.31.*, 192.168.*
Loopback: 127.*
Arguments:
ipAddr - IP address to be checked
"""
# checks for any of the simple wildcard ranges
if ipAddr.startswith("10.") or ipAddr.startswith("192.168.") or ipAddr.startswith("127."):
return True
# checks for the 172.16.* - 172.31.* range
if ipAddr.startswith("172.") and ipAddr.count(".") == 3:
secondOctet = ipAddr[4:ipAddr.find(".", 4)]
if secondOctet.isdigit() and int(secondOctet) >= 16 and int(secondOctet) <= 31:
return True
return False
def ipToInt(ipAddr):
"""
Provides an integer representation of the ip address, suitable for sorting.
Arguments:
ipAddr - ip address to be converted
"""
total = 0
for comp in ipAddr.split("."):
total *= 255
total += int(comp)
return total
def getPortUsage(port):
"""
Provides the common use of a given port. If no useage is known then this
provides None.
Arguments:
port - port number to look up
"""
return PORT_USAGE.get(port)
def getResolverCommand(resolutionCmd, processName, processPid = ""):
"""
Provides the command that would be processed for the given resolver type.
This raises a ValueError if either the resolutionCmd isn't recognized or a
pid was requited but not provided.
Arguments:
resolutionCmd - command to use in resolving the address
processName - name of the process for which connections are fetched
processPid - process ID (this helps improve accuracy)
"""
if not processPid:
# the pid is required for procstat resolution
if resolutionCmd == Resolver.BSD_PROCSTAT:
raise ValueError("procstat resolution requires a pid")
# if the pid was undefined then match any in that field
processPid = "[0-9]*"
if resolutionCmd == Resolver.PROC: return ""
elif resolutionCmd == Resolver.NETSTAT: return RUN_NETSTAT % (processPid, processName)
elif resolutionCmd == Resolver.SS: return RUN_SS % (processName, processPid)
elif resolutionCmd == Resolver.LSOF: return RUN_LSOF % (processName, processPid)
elif resolutionCmd == Resolver.SOCKSTAT: return RUN_SOCKSTAT % (processName, processPid)
elif resolutionCmd == Resolver.BSD_SOCKSTAT: return RUN_BSD_SOCKSTAT % (processName, processPid)
elif resolutionCmd == Resolver.BSD_PROCSTAT: return RUN_BSD_PROCSTAT % processPid
else: raise ValueError("Unrecognized resolution type: %s" % resolutionCmd)
def getConnections(resolutionCmd, processName, processPid = ""):
"""
Retrieves a list of the current connections for a given process, providing a
tuple list of the form:
[(local_ipAddr1, local_port1, foreign_ipAddr1, foreign_port1), ...]
this raises an IOError if no connections are available or resolution fails
(in most cases these appear identical). Common issues include:
- insufficient permissions
- resolution command is unavailable
- usage of the command is non-standard (particularly an issue for BSD)
Arguments:
resolutionCmd - command to use in resolving the address
processName - name of the process for which connections are fetched
processPid - process ID (this helps improve accuracy)
"""
if resolutionCmd == Resolver.PROC:
# Attempts resolution via checking the proc contents.
if not processPid:
raise ValueError("proc resolution requires a pid")
try:
return procTools.getConnections(processPid)
except Exception, exc:
raise IOError(str(exc))
else:
# Queries a resolution utility (netstat, lsof, etc). This raises an
# IOError if the command fails or isn't available.
cmd = getResolverCommand(resolutionCmd, processName, processPid)
results = sysTools.call(cmd)
if not results: raise IOError("No results found using: %s" % cmd)
# parses results for the resolution command
conn = []
for line in results:
if resolutionCmd == Resolver.LSOF:
# Different versions of lsof have different numbers of columns, so
# stripping off the optional 'established' entry so we can just use
# the last one.
comp = line.replace("(ESTABLISHED)", "").strip().split()
else: comp = line.split()
if resolutionCmd == Resolver.NETSTAT:
localIp, localPort = comp[3].split(":")
foreignIp, foreignPort = comp[4].split(":")
elif resolutionCmd == Resolver.SS:
localIp, localPort = comp[4].split(":")
foreignIp, foreignPort = comp[5].split(":")
elif resolutionCmd == Resolver.LSOF:
local, foreign = comp[-1].split("->")
localIp, localPort = local.split(":")
foreignIp, foreignPort = foreign.split(":")
elif resolutionCmd == Resolver.SOCKSTAT:
localIp, localPort = comp[4].split(":")
foreignIp, foreignPort = comp[5].split(":")
elif resolutionCmd == Resolver.BSD_SOCKSTAT:
localIp, localPort = comp[5].split(":")
foreignIp, foreignPort = comp[6].split(":")
elif resolutionCmd == Resolver.BSD_PROCSTAT:
localIp, localPort = comp[9].split(":")
foreignIp, foreignPort = comp[10].split(":")
conn.append((localIp, localPort, foreignIp, foreignPort))
return conn
def isResolverAlive(processName, processPid = ""):
"""
This provides true if a singleton resolver instance exists for the given
process/pid combination, false otherwise.
Arguments:
processName - name of the process being checked
processPid - pid of the process being checked, if undefined this matches
against any resolver with the process name
"""
for resolver in RESOLVERS:
if not resolver._halt and resolver.processName == processName and (not processPid or resolver.processPid == processPid):
return True
return False
def getResolver(processName, processPid = "", alias=None):
"""
Singleton constructor for resolver instances. If a resolver already exists
for the process then it's returned. Otherwise one is created and started.
Arguments:
processName - name of the process being resolved
processPid - pid of the process being resolved, if undefined this matches
against any resolver with the process name
alias - alternative handle under which the resolver can be requested
"""
# check if one's already been created
requestHandle = alias if alias else processName
haltedIndex = -1 # old instance of this resolver with the _halt flag set
for i in range(len(RESOLVERS)):
resolver = RESOLVERS[i]
if resolver.handle == requestHandle and (not processPid or resolver.processPid == processPid):
if resolver._halt and RECREATE_HALTED_RESOLVERS: haltedIndex = i
else: return resolver
# make a new resolver
r = ConnectionResolver(processName, processPid, handle = requestHandle)
r.start()
# overwrites halted instance of this resolver if it exists, otherwise append
if haltedIndex == -1: RESOLVERS.append(r)
else: RESOLVERS[haltedIndex] = r
return r
def getSystemResolvers(osType = None):
"""
Provides the types of connection resolvers available on this operating
system.
Arguments:
osType - operating system type, fetched from the os module if undefined
"""
if osType == None: osType = os.uname()[0]
if osType == "FreeBSD":
resolvers = [Resolver.BSD_SOCKSTAT, Resolver.BSD_PROCSTAT, Resolver.LSOF]
elif osType in ("OpenBSD", "Darwin"):
resolvers = [Resolver.LSOF]
else:
resolvers = [Resolver.NETSTAT, Resolver.SOCKSTAT, Resolver.LSOF, Resolver.SS]
# proc resolution, by far, outperforms the others so defaults to this is able
if procTools.isProcAvailable():
resolvers = [Resolver.PROC] + resolvers
return resolvers
class ConnectionResolver(threading.Thread):
"""
Service that periodically queries for a process' current connections. This
provides several benefits over on-demand queries:
- queries are non-blocking (providing cached results)
- falls back to use different resolution methods in case of repeated failures
- avoids overly frequent querying of connection data, which can be demanding
in terms of system resources
Unless an overriding method of resolution is requested this defaults to
choosing a resolver the following way:
- Checks the current PATH to determine which resolvers are available. This
uses the first of the following that's available:
netstat, ss, lsof (picks netstat if none are found)
- Attempts to resolve using the selection. Single failures are logged at the
INFO level, and a series of failures at NOTICE. In the later case this
blacklists the resolver, moving on to the next. If all resolvers fail this
way then resolution's abandoned and logs a WARN message.
The time between resolving connections, unless overwritten, is set to be
either five seconds or ten times the runtime of the resolver (whichever is
larger). This is to prevent systems either strapped for resources or with a
vast number of connections from being burdened too heavily by this daemon.
Parameters:
processName - name of the process being resolved
processPid - pid of the process being resolved
resolveRate - minimum time between resolving connections (in seconds,
None if using the default)
* defaultRate - default time between resolving connections
lastLookup - time connections were last resolved (unix time, -1 if
no resolutions have yet been successful)
overwriteResolver - method of resolution (uses default if None)
* defaultResolver - resolver used by default (None if all resolution
methods have been exhausted)
resolverOptions - resolvers to be cycled through (differ by os)
* read-only
"""
def __init__(self, processName, processPid = "", resolveRate = None, handle = None):
"""
Initializes a new resolver daemon. When no longer needed it's suggested
that this is stopped.
Arguments:
processName - name of the process being resolved
processPid - pid of the process being resolved
resolveRate - time between resolving connections (in seconds, None if
chosen dynamically)
handle - name used to query this resolver, this is the processName
if undefined
"""
threading.Thread.__init__(self)
self.setDaemon(True)
self.processName = processName
self.processPid = processPid
self.resolveRate = resolveRate
self.handle = handle if handle else processName
self.defaultRate = CONFIG["queries.connections.minRate"]
self.lastLookup = -1
self.overwriteResolver = None
self.defaultResolver = Resolver.PROC
osType = os.uname()[0]
self.resolverOptions = getSystemResolvers(osType)
log.log(CONFIG["log.connResolverOptions"], "Operating System: %s, Connection Resolvers: %s" % (osType, ", ".join(self.resolverOptions)))
# sets the default resolver to be the first found in the system's PATH
# (left as netstat if none are found)
for resolver in self.resolverOptions:
# Resolver strings correspond to their command with the exception of bsd
# resolvers.
resolverCmd = resolver.replace(" (bsd)", "")
if resolver == Resolver.PROC or sysTools.isAvailable(resolverCmd):
self.defaultResolver = resolver
break
self._connections = [] # connection cache (latest results)
self._resolutionCounter = 0 # number of successful connection resolutions
self._isPaused = False
self._halt = False # terminates thread if true
self._cond = threading.Condition() # used for pausing the thread
self._subsiquentFailures = 0 # number of failed resolutions with the default in a row
self._resolverBlacklist = [] # resolvers that have failed to resolve
# Number of sequential times the threshold rate's been too low. This is to
# avoid having stray spikes up the rate.
self._rateThresholdBroken = 0
def getOverwriteResolver(self):
"""
Provides the resolver connection resolution is forced to use. This returns
None if it's dynamically determined.
"""
return self.overwriteResolver
def setOverwriteResolver(self, overwriteResolver):
"""
Sets the resolver used for connection resolution, if None then this is
automatically determined based on what is available.
Arguments:
overwriteResolver - connection resolver to be used
"""
self.overwriteResolver = overwriteResolver
def run(self):
while not self._halt:
minWait = self.resolveRate if self.resolveRate else self.defaultRate
timeSinceReset = time.time() - self.lastLookup
if self._isPaused or timeSinceReset < minWait:
sleepTime = max(0.2, minWait - timeSinceReset)
self._cond.acquire()
if not self._halt: self._cond.wait(sleepTime)
self._cond.release()
continue # done waiting, try again
isDefault = self.overwriteResolver == None
resolver = self.defaultResolver if isDefault else self.overwriteResolver
# checks if there's nothing to resolve with
if not resolver:
self.lastLookup = time.time() # avoids a busy wait in this case
continue
try:
resolveStart = time.time()
connResults = getConnections(resolver, self.processName, self.processPid)
lookupTime = time.time() - resolveStart
self._connections = connResults
self._resolutionCounter += 1
newMinDefaultRate = 100 * lookupTime
if self.defaultRate < newMinDefaultRate:
if self._rateThresholdBroken >= 3:
# adding extra to keep the rate from frequently changing
self.defaultRate = newMinDefaultRate + 0.5
msg = "connection lookup time increasing to %0.1f seconds per call" % self.defaultRate
log.log(CONFIG["log.connLookupRateGrowing"], msg)
else: self._rateThresholdBroken += 1
else: self._rateThresholdBroken = 0
if isDefault: self._subsiquentFailures = 0
except (ValueError, IOError), exc:
# this logs in a couple of cases:
# - special failures noted by getConnections (most cases are already
# logged via sysTools)
# - note fail-overs for default resolution methods
if str(exc).startswith("No results found using:"):
log.log(CONFIG["log.connLookupFailed"], str(exc))
if isDefault:
self._subsiquentFailures += 1
if self._subsiquentFailures >= RESOLVER_FAILURE_TOLERANCE:
# failed several times in a row - abandon resolver and move on to another
self._resolverBlacklist.append(resolver)
self._subsiquentFailures = 0
# pick another (non-blacklisted) resolver
newResolver = None
for r in self.resolverOptions:
if not r in self._resolverBlacklist:
newResolver = r
break
if newResolver:
# provide notice that failures have occurred and resolver is changing
msg = RESOLVER_SERIAL_FAILURE_MSG % (resolver, newResolver)
log.log(CONFIG["log.connLookupFailover"], msg)
else:
# exhausted all resolvers, give warning
log.log(CONFIG["log.connLookupAbandon"], RESOLVER_FINAL_FAILURE_MSG)
self.defaultResolver = newResolver
finally:
self.lastLookup = time.time()
def getConnections(self):
"""
Provides the last queried connection results, an empty list if resolver
has been halted.
"""
if self._halt: return []
else: return list(self._connections)
def getResolutionCount(self):
"""
Provides the number of successful resolutions so far. This can be used to
determine if the connection results are new for the caller or not.
"""
return self._resolutionCounter
def getPid(self):
"""
Provides the pid used to narrow down connection resolution. This is an
empty string if undefined.
"""
return self.processPid
def setPid(self, processPid):
"""
Sets the pid used to narrow down connection resultions.
Arguments:
processPid - pid for the process we're fetching connections for
"""
self.processPid = processPid
def setPaused(self, isPause):
"""
Allows or prevents further connection resolutions (this still makes use of
cached results).
Arguments:
isPause - puts a freeze on further resolutions if true, allows them to
continue otherwise
"""
if isPause == self._isPaused: return
self._isPaused = isPause
def stop(self):
"""
Halts further resolutions and terminates the thread.
"""
self._cond.acquire()
self._halt = True
self._cond.notifyAll()
self._cond.release()
class AppResolver:
"""
Provides the names and pids of appliations attached to the given ports. This
stops attempting to query if it fails three times without successfully
getting lsof results.
"""
def __init__(self, scriptName = "python"):
"""
Constructs a resolver instance.
Arguments:
scriptName - name by which to all our own entries
"""
self.scriptName = scriptName
self.queryResults = {}
self.resultsLock = threading.RLock()
self._cond = threading.Condition() # used for pausing when waiting for results
self.isResolving = False # flag set if we're in the process of making a query
self.failureCount = 0 # -1 if we've made a successful query
def getResults(self, maxWait=0):
"""
Provides the last queried results. If we're in the process of making a
query then we can optionally block for a time to see if it finishes.
Arguments:
maxWait - maximum second duration to block on getting results before
returning
"""
self._cond.acquire()
if self.isResolving and maxWait > 0:
self._cond.wait(maxWait)
self._cond.release()
self.resultsLock.acquire()
results = dict(self.queryResults)
self.resultsLock.release()
return results
def resolve(self, ports):
"""
Queues the given listing of ports to be resolved. This clears the last set
of results when completed.
Arguments:
ports - list of ports to be resolved to applications
"""
if self.failureCount < 3:
self.isResolving = True
t = threading.Thread(target = self._queryApplications, kwargs = {"ports": ports})
t.setDaemon(True)
t.start()
def _queryApplications(self, ports=[]):
"""
Performs an lsof lookup on the given ports to get the command/pid tuples.
Arguments:
ports - list of ports to be resolved to applications
"""
# atagar@fenrir:~/Desktop/arm$ lsof -i tcp:51849 -i tcp:37277
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# tor 2001 atagar 14u IPv4 14048 0t0 TCP localhost:9051->localhost:37277 (ESTABLISHED)
# tor 2001 atagar 15u IPv4 22024 0t0 TCP localhost:9051->localhost:51849 (ESTABLISHED)
# python 2462 atagar 3u IPv4 14047 0t0 TCP localhost:37277->localhost:9051 (ESTABLISHED)
# python 3444 atagar 3u IPv4 22023 0t0 TCP localhost:51849->localhost:9051 (ESTABLISHED)
if not ports:
self.resultsLock.acquire()
self.queryResults = {}
self.isResolving = False
self.resultsLock.release()
# wakes threads waiting on results
self._cond.acquire()
self._cond.notifyAll()
self._cond.release()
return
results = {}
lsofArgs = []
# Uses results from the last query if we have any, otherwise appends the
# port to the lsof command. This has the potential for persisting dirty
# results but if we're querying by the dynamic port on the local tcp
# connections then this should be very rare (and definitely worth the
# chance of being able to skip an lsof query altogether).
for port in ports:
if port in self.queryResults:
results[port] = self.queryResults[port]
else: lsofArgs.append("-i tcp:%s" % port)
if lsofArgs:
lsofResults = sysTools.call("lsof -nP " + " ".join(lsofArgs))
else: lsofResults = None
if not lsofResults and self.failureCount != -1:
# lsof query failed and we aren't yet sure if it's possible to
# successfully get results on this platform
self.failureCount += 1
self.isResolving = False
return
elif lsofResults:
# (iPort, oPort) tuple for our own process, if it was fetched
ourConnection = None
for line in lsofResults:
lineComp = line.split()
if len(lineComp) == 10 and lineComp[9] == "(ESTABLISHED)":
cmd, pid, _, _, _, _, _, _, portMap, _ = lineComp
if "->" in portMap:
iPort, oPort = portMap.split("->")
iPort = iPort.split(":")[1]
oPort = oPort.split(":")[1]
# entry belongs to our own process
if pid == str(os.getpid()):
cmd = self.scriptName
ourConnection = (iPort, oPort)
if iPort.isdigit() and oPort.isdigit():
newEntry = (iPort, oPort, cmd, pid)
# adds the entry under the key of whatever we queried it with
# (this might be both the inbound _and_ outbound ports)
for portMatch in (iPort, oPort):
if portMatch in ports:
if portMatch in results:
results[portMatch].append(newEntry)
else: results[portMatch] = [newEntry]
# making the lsof call generated an extraneous sh entry for our own connection
if ourConnection:
for ourPort in ourConnection:
if ourPort in results:
shIndex = None
for i in range(len(results[ourPort])):
if results[ourPort][i][2] == "sh":
shIndex = i
break
if shIndex != None:
del results[ourPort][shIndex]
self.resultsLock.acquire()
self.failureCount = -1
self.queryResults = results
self.isResolving = False
self.resultsLock.release()
# wakes threads waiting on results
self._cond.acquire()
self._cond.notifyAll()
self._cond.release()
|