Add configurable interval for nmap_tracker when devices are home.

Adds an option that will exclude devices from the periodic nmap scan
for the configured number of minutes.
This commit is contained in:
andythigpen 2015-03-07 14:52:54 -06:00
parent c3c07d621a
commit bfb5089ed5

View File

@ -1,6 +1,6 @@
""" Supports scanning using nmap. """ """ Supports scanning using nmap. """
import logging import logging
from datetime import timedelta from datetime import timedelta, datetime
import threading import threading
from collections import namedtuple from collections import namedtuple
import subprocess import subprocess
@ -11,7 +11,7 @@ from libnmap.parser import NmapParser, NmapParserException
from homeassistant.const import CONF_HOSTS from homeassistant.const import CONF_HOSTS
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.util import Throttle from homeassistant.util import Throttle, convert
from homeassistant.components.device_tracker import DOMAIN from homeassistant.components.device_tracker import DOMAIN
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago
@ -19,6 +19,9 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# interval in minutes to exclude devices from a scan while they are home
CONF_HOME_INTERVAL = "home_interval"
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a Nmap scanner. """ """ Validates config and returns a Nmap scanner. """
@ -30,7 +33,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None return scanner if scanner.success_init else None
Device = namedtuple("Device", ["mac", "name"]) Device = namedtuple("Device", ["mac", "name", "ip", "last_update"])
def _arp(ip_address): def _arp(ip_address):
@ -53,6 +56,8 @@ class NmapDeviceScanner(object):
self.lock = threading.Lock() self.lock = threading.Lock()
self.hosts = config[CONF_HOSTS] self.hosts = config[CONF_HOSTS]
minutes = convert(config.get(CONF_HOME_INTERVAL), int, 0)
self.home_interval = timedelta(minutes=minutes)
self.success_init = True self.success_init = True
self._update_info() self._update_info()
@ -77,23 +82,12 @@ class NmapDeviceScanner(object):
else: else:
return None return None
@Throttle(MIN_TIME_BETWEEN_SCANS) def _parse_results(self, stdout):
def _update_info(self): """ Parses results from an nmap scan.
""" Scans the network for devices. Returns True if successful, False otherwise. """
Returns boolean if scanning successful. """
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Scanning")
nmap = NmapProcess(targets=self.hosts, options="-F")
nmap.run()
if nmap.rc == 0:
try: try:
results = NmapParser.parse(nmap.stdout) results = NmapParser.parse(stdout)
now = datetime.now()
self.last_results = [] self.last_results = []
for host in results.hosts: for host in results.hosts:
if host.is_up(): if host.is_up():
@ -106,16 +100,43 @@ class NmapDeviceScanner(object):
else: else:
mac = _arp(host.ipv4) mac = _arp(host.ipv4)
if mac: if mac:
device = Device(mac, name) device = Device(mac, name, host.ipv4, now)
self.last_results.append(device) self.last_results.append(device)
_LOGGER.info("nmap scan successful") _LOGGER.info("nmap scan successful")
return True return True
except NmapParserException as parse_exc: except NmapParserException as parse_exc:
_LOGGER.error("failed to parse nmap results: %s", _LOGGER.error("failed to parse nmap results: %s", parse_exc.msg)
parse_exc.msg)
self.last_results = [] self.last_results = []
return False return False
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Scans the network for devices.
Returns boolean if scanning successful. """
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Scanning")
options = "-F"
exclude_targets = set()
if self.home_interval:
now = datetime.now()
for host in self.last_results:
if host.last_update + self.home_interval > now:
exclude_targets.add(host)
if len(exclude_targets) > 0:
target_list = [t.ip for t in exclude_targets]
options += " --exclude {}".format(",".join(target_list))
nmap = NmapProcess(targets=self.hosts, options=options)
nmap.run()
if nmap.rc == 0:
if self._parse_results(nmap.stdout):
self.last_results.extend(exclude_targets)
else: else:
self.last_results = [] self.last_results = []
_LOGGER.error(nmap.stderr) _LOGGER.error(nmap.stderr)