diff --git a/Dockerfile b/Dockerfile index 8ce295ae6aa..9554ec552d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,12 @@ VOLUME /config RUN pip3 install --no-cache-dir -r requirements_all.txt +# For the nmap tracker +RUN apt-get update && \ + apt-get install -y --no-install-recommends nmap net-tools && \ + apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Open Z-Wave disabled because broken #RUN apt-get update && \ # apt-get install -y cython3 libudev-dev && \ # apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 234863c571c..fd706b3d73a 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -10,7 +10,6 @@ import os import csv from datetime import timedelta -from homeassistant.loader import get_component from homeassistant.helpers import validate_config from homeassistant.helpers.entity import _OVERWRITE import homeassistant.util as util @@ -63,7 +62,7 @@ def setup(hass, config): return False tracker_type = config[DOMAIN].get(CONF_PLATFORM) - + tracker_implementation = \ prepare_setup_platform(hass, config, DOMAIN, tracker_type) diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index f2da939a79e..7a795deae7b 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/device_tracker/nmap_tracker.py @@ -19,6 +19,11 @@ hosts *Required The IP addresses to scan in the network-prefix notation (192.168.1.1/24) or the range notation (192.168.1.1-255). + +home_interval +*Optional +Number of minutes it will not scan devices that it found in previous results. +This is to save battery. """ import logging from datetime import timedelta @@ -40,22 +45,15 @@ _LOGGER = logging.getLogger(__name__) # interval in minutes to exclude devices from a scan while they are home CONF_HOME_INTERVAL = "home_interval" -REQUIREMENTS = ['python-libnmap==0.6.1'] +REQUIREMENTS = ['python-nmap==0.4.1'] def get_scanner(hass, config): """ Validates config and returns a Nmap scanner. """ - # pylint: disable=unused-variable if not validate_config(config, {DOMAIN: [CONF_HOSTS]}, _LOGGER): return None - try: - import libnmap # noqa - except ImportError: - _LOGGER.error("Error while importing dependency python-libnmap.") - return None - scanner = NmapDeviceScanner(config[DOMAIN]) return scanner if scanner.success_init else None @@ -72,28 +70,20 @@ def _arp(ip_address): if match: return match.group(0) _LOGGER.info("No MAC address found for %s", ip_address) - return '' + return None class NmapDeviceScanner(object): """ This class scans for devices using nmap """ def __init__(self, config): - from libnmap.process import NmapProcess - from libnmap.parser import NmapParser, NmapParserException - - self.nmap_process = NmapProcess - self.nmap_parser = NmapParser - self.nmap_parser_exception = NmapParserException - self.last_results = [] 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._update_info() + self.success_init = self._update_info() _LOGGER.info("nmap scanner initialized") def scan_devices(self): @@ -115,43 +105,16 @@ class NmapDeviceScanner(object): else: return None - def _parse_results(self, stdout): - """ Parses results from an nmap scan. - Returns True if successful, False otherwise. """ - try: - results = self.nmap_parser.parse(stdout) - now = dt_util.now() - self.last_results = [] - for host in results.hosts: - if host.is_up(): - if host.hostnames: - name = host.hostnames[0] - else: - name = host.ipv4 - if host.mac: - mac = host.mac - else: - mac = _arp(host.ipv4) - if mac: - device = Device(mac.upper(), name, host.ipv4, now) - self.last_results.append(device) - _LOGGER.info("nmap scan successful") - return True - except self.nmap_parser_exception as parse_exc: - _LOGGER.error("failed to parse nmap results: %s", parse_exc.msg) - self.last_results = [] - 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 - _LOGGER.info("Scanning") - options = "-F --host-timeout 5" + from nmap import PortScanner, PortScannerError + scanner = PortScanner() + + options = "-sP --host-timeout 5" exclude_targets = set() if self.home_interval: now = dt_util.now() @@ -162,14 +125,24 @@ class NmapDeviceScanner(object): target_list = [t.ip for t in exclude_targets] options += " --exclude {}".format(",".join(target_list)) - nmap = self.nmap_process(targets=self.hosts, options=options) - - nmap.run() - - if nmap.rc == 0: - if self._parse_results(nmap.stdout): - self.last_results.extend(exclude_targets) - else: - self.last_results = [] - _LOGGER.error(nmap.stderr) + try: + result = scanner.scan(hosts=self.hosts, arguments=options) + except PortScannerError: return False + + now = dt_util.now() + self.last_results = [] + for ipv4, info in result['scan'].items(): + if info['status']['state'] != 'up': + continue + name = info['hostnames'][0] if info['hostnames'] else ipv4 + # Mac address only returned if nmap ran as root + mac = info['addresses'].get('mac') or _arp(ipv4) + if mac is None: + continue + device = Device(mac.upper(), name, ipv4, now) + self.last_results.append(device) + self.last_results.extend(exclude_targets) + + _LOGGER.info("nmap scan successful") + return True diff --git a/requirements_all.txt b/requirements_all.txt index ce74a8475cd..53e830fccde 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -25,7 +25,7 @@ pyuserinput==0.1.9 tellcore-py==1.0.4 # Nmap bindings (device_tracker.nmap) -python-libnmap==0.6.3 +python-nmap==0.4.1 # PushBullet bindings (notify.pushbullet) pushbullet.py==0.7.1