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/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index ee1650594ee..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 @@ -26,13 +31,6 @@ from collections import namedtuple import subprocess import re -try: - from libnmap.process import NmapProcess - from libnmap.parser import NmapParser, NmapParserException - LIB_LOADED = True -except ImportError: - LIB_LOADED = False - import homeassistant.util.dt as dt_util from homeassistant.const import CONF_HOSTS from homeassistant.helpers import validate_config @@ -47,7 +45,7 @@ _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): @@ -56,10 +54,6 @@ def get_scanner(hass, config): _LOGGER): return None - if not LIB_LOADED: - _LOGGER.error("Error while importing dependency python-libnmap.") - return False - scanner = NmapDeviceScanner(config[DOMAIN]) return scanner if scanner.success_init else None @@ -76,7 +70,7 @@ 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): @@ -89,8 +83,7 @@ class NmapDeviceScanner(object): 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): @@ -112,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 = NmapParser.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 NmapParserException 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() @@ -159,14 +125,24 @@ class NmapDeviceScanner(object): 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: - 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