From ffac067be80aa11a4bd968b22f135c25e14b37ce Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 31 Aug 2015 00:29:41 -0700 Subject: [PATCH 1/5] Migrate nmap_tracker to use different nmap lib --- .../components/device_tracker/nmap_tracker.py | 90 +++++++------------ 1 file changed, 34 insertions(+), 56 deletions(-) diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index ee1650594ee..31f60bf032b 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,26 @@ 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 ip, info in result['scan'].items(): + if info['status']['state'] != 'up': + continue + name = info['hostnames'][0] if info['hostnames'] else ip + # Mac address only returned if nmap ran as root + mac = info['addresses'].get('mac') + if mac is None: + mac = _arp(ip) + if mac is None: + continue + device = Device(mac.upper(), name, ip, now) + self.last_results.append(device) + self.last_results.extend(exclude_targets) + + _LOGGER.info("nmap scan successful") + return True From 78826648e3056110a230a86df830f639eb44bd0c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 31 Aug 2015 00:44:59 -0700 Subject: [PATCH 2/5] style + dependency fix --- homeassistant/components/device_tracker/nmap_tracker.py | 8 ++++---- requirements_all.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index 31f60bf032b..8724f3a196a 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/device_tracker/nmap_tracker.py @@ -132,17 +132,17 @@ class NmapDeviceScanner(object): now = dt_util.now() self.last_results = [] - for ip, info in result['scan'].items(): + for ipv4, info in result['scan'].items(): if info['status']['state'] != 'up': continue - name = info['hostnames'][0] if info['hostnames'] else ip + name = info['hostnames'][0] if info['hostnames'] else ipv4 # Mac address only returned if nmap ran as root mac = info['addresses'].get('mac') if mac is None: - mac = _arp(ip) + mac = _arp(ipv4) if mac is None: continue - device = Device(mac.upper(), name, ip, now) + device = Device(mac.upper(), name, ipv4, now) self.last_results.append(device) self.last_results.extend(exclude_targets) diff --git a/requirements_all.txt b/requirements_all.txt index 9798a95f030..92a80edbb72 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 From b41706efe3cab1510cfc86a4a2c3b9ef55f56a9c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 31 Aug 2015 22:01:45 -0700 Subject: [PATCH 3/5] Make nmap work in Docker --- Dockerfile | 6 ++++++ homeassistant/components/device_tracker/nmap_tracker.py | 4 +--- 2 files changed, 7 insertions(+), 3 deletions(-) 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 8724f3a196a..7a795deae7b 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/device_tracker/nmap_tracker.py @@ -137,9 +137,7 @@ class NmapDeviceScanner(object): continue name = info['hostnames'][0] if info['hostnames'] else ipv4 # Mac address only returned if nmap ran as root - mac = info['addresses'].get('mac') - if mac is None: - mac = _arp(ipv4) + mac = info['addresses'].get('mac') or _arp(ipv4) if mac is None: continue device = Device(mac.upper(), name, ipv4, now) From 83440ad718492a686375a8813d3eea6ab46ea277 Mon Sep 17 00:00:00 2001 From: Ryan Kraus Date: Sun, 30 Aug 2015 20:22:02 -0400 Subject: [PATCH 4/5] Fixed device tracker to install scanner requirements. --- homeassistant/components/device_tracker/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 099c23973f0..234863c571c 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -15,6 +15,7 @@ from homeassistant.helpers import validate_config from homeassistant.helpers.entity import _OVERWRITE import homeassistant.util as util import homeassistant.util.dt as dt_util +from homeassistant.bootstrap import prepare_setup_platform from homeassistant.helpers.event import track_utc_time_change from homeassistant.const import ( @@ -62,9 +63,9 @@ def setup(hass, config): return False tracker_type = config[DOMAIN].get(CONF_PLATFORM) - - tracker_implementation = get_component( - 'device_tracker.{}'.format(tracker_type)) + + tracker_implementation = \ + prepare_setup_platform(hass, config, DOMAIN, tracker_type) if tracker_implementation is None: _LOGGER.error("Unknown device_tracker type specified: %s.", From a34b00bc9cbc6f9cfcadbbeb7aa8760dfb6f9436 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 31 Aug 2015 22:20:24 -0700 Subject: [PATCH 5/5] Style fix for device tracker --- homeassistant/components/device_tracker/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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)