# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2017-2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This is extracting the confluent slp support into
# a standalone script, for use in environments that
# can't run full confluent and/or would prefer
# a utility style approach to SLP


import os
import random
import select
import socket
import struct

_slp_services = set([
    'service:management-hardware.IBM:integrated-management-module2',
    'service:lenovo-smm',
    'service:lenovo-smm2',
    'service:ipmi',
    'service:lighttpd',
    'service:management-hardware.Lenovo:lenovo-xclarity-controller',
    'service:management-hardware.IBM:chassis-management-module',
    'service:management-hardware.Lenovo:chassis-management-module',
    'service:io-device.Lenovo:management-module',
])

# SLP has a lot of ambition that was unfulfilled in practice.
# So we have a static footer here to always use 'DEFAULT' scope, no LDAP
# predicates, and no authentication for service requests
srvreqfooter = b'\x00\x07DEFAULT\x00\x00\x00\x00'
# An empty instance of the attribute list extension
# which is defined in RFC 3059, used to indicate support for that capability
attrlistext = b'\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00'

try:
    IPPROTO_IPV6 = socket.IPPROTO_IPV6
except AttributeError:
    IPPROTO_IPV6 = 41  # Assume Windows value if socket is missing it


def _parse_slp_header(packet):
    packet = bytearray(packet)
    if len(packet) < 16 or packet[0] != 2:
        # discard packets that are obviously useless
        return None
    parsed = {
        'function': packet[1],
    }
    (offset, parsed['xid'], langlen) = struct.unpack('!IHH',
                                           bytes(b'\x00' + packet[7:14]))
    parsed['lang'] = packet[14:14 + langlen].decode('utf-8')
    parsed['payload'] = packet[14 + langlen:]
    if offset:
        parsed['offset'] = 14 + langlen
        parsed['extoffset'] = offset
    return parsed


def _pop_url(payload):
    urllen = struct.unpack('!H', bytes(payload[3:5]))[0]
    url = bytes(payload[5:5+urllen]).decode('utf-8')
    if payload[5+urllen] != 0:
        raise Exception('Auth blocks unsupported')
    payload = payload[5+urllen+1:]
    return url, payload


def _parse_SrvRply(parsed):
    """ Modify passed dictionary to have parsed data


    :param parsed:
    :return:
    """
    payload = parsed['payload']
    if len(payload) < 4:
        return
    ecode, ucount = struct.unpack('!HH', bytes(payload[0:4]))
    if ecode:
        parsed['errorcode'] = ecode
    payload = payload[4:]
    parsed['urls'] = []
    while ucount:
        ucount -= 1
        url, payload = _pop_url(payload)
        parsed['urls'].append(url)


def _parse_slp_packet(packet, peer, rsps, xidmap):
    parsed = _parse_slp_header(packet)
    if not parsed:
        return
    addr = peer[0]
    if '%' in addr:
        addr = addr[:addr.index('%')]
    mac = None
    if addr in neightable:
        identifier = neightable[addr]
        mac = identifier
    else:
        identifier = addr
    if (identifier, parsed['xid']) in rsps:
        # avoid obviously duplicate entries
        parsed = rsps[(identifier, parsed['xid'])]
    else:
        rsps[(identifier, parsed['xid'])] = parsed
    if mac and 'hwaddr' not in parsed:
        parsed['hwaddr'] = mac
    if parsed['xid'] in xidmap:
        parsed['services'] = [xidmap[parsed['xid']]]
    if 'addresses' in parsed:
        if peer not in parsed['addresses']:
            parsed['addresses'].append(peer)
    else:
        parsed['addresses'] = [peer]
    if parsed['function'] == 2:  # A service reply
        _parse_SrvRply(parsed)


def _v6mcasthash(srvtype):
    # The hash algorithm described by RFC 3111
    nums = bytearray(srvtype.encode('utf-8'))
    hashval = 0
    for i in nums:
        hashval *= 33
        hashval += i
        hashval &= 0xffff  # only need to track the lowest 16 bits
    hashval &= 0x3ff
    hashval |= 0x1000
    return '{0:x}'.format(hashval)


def _generate_slp_header(payload, multicast, functionid, xid, extoffset=0):
    if multicast:
        flags = 0x2000
    else:
        flags = 0
    packetlen = len(payload) + 16  # we have a fixed 16 byte header supported
    if extoffset:  # if we have an offset, add 16 to account for this function
        # generating a 16 byte header
        extoffset += 16
    if packetlen > 1400:
        # For now, we aren't intending to support large SLP transmits
        # raise an exception to help identify if such a requirement emerges
        raise Exception("TODO: Transmit overflow packets")
    # We always do SLP v2, and only v2
    header = bytearray([2, functionid])
    # SLP uses 24 bit packed integers, so in such places we pack 32 then
    # discard the high byte
    header.extend(struct.pack('!IH', packetlen, flags)[1:])
    # '2' below refers to the length of the language tag
    header.extend(struct.pack('!IHH', extoffset, xid, 2)[1:])
    # we only do english (in SLP world, it's not like non-english appears...)
    header.extend(b'en')
    return header

def _generate_attr_request(service, xid):
    service = service.encode('utf-8')
    payload = bytearray(struct.pack('!HH', 0, len(service)) + service)
    payload.extend(srvreqfooter)
    header = _generate_slp_header(payload, False, functionid=6, xid=xid)
    return header + payload



def _generate_request_payload(srvtype, multicast, xid, prlist=''):
    prlist = prlist.encode('utf-8')
    payload = bytearray(struct.pack('!H', len(prlist)) + prlist)
    srvtype = srvtype.encode('utf-8')
    payload.extend(struct.pack('!H', len(srvtype)) + srvtype)
    payload.extend(srvreqfooter)
    extoffset = len(payload)
    payload.extend(attrlistext)
    header = _generate_slp_header(payload, multicast, functionid=1, xid=xid,
                                  extoffset=extoffset)
    return header + payload


def _find_srvtype(net, net4, srvtype, addresses, xid):
    """Internal function to find a single service type

    Helper to do singleton requests to srvtype

    :param net: Socket active
    :param srvtype: Service type to do now
    :param addresses:  Pass through of addresses argument from find_targets
    :return:
    """
    data = _generate_request_payload(srvtype, True, xid)
    if addresses is not None:
        for addr in addresses:
            for saddr in socket.getaddrinfo(addr, 427):
                if saddr[0] == socket.AF_INET:
                    net4.sendto(data, saddr[4])
                elif saddr[0] == socket.AF_INET6:
                    net.sendto(data, saddr[4])
    else:
        net4.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        v6addrs = []
        v6hash = _v6mcasthash(srvtype)
        # do 'interface local' and 'link local'
        # it shouldn't make sense, but some configurations work with interface
        # local that do not work with link local
        v6addrs.append(('ff01::1:' + v6hash, 427, 0, 0))
        v6addrs.append(('ff02::1:' + v6hash, 427, 0, 0))
        for idx in list_interface_indexes():
            # IPv6 multicast is by index, so lead with that
            net.setsockopt(IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, idx)
            for sa in v6addrs:
                try:
                    net.sendto(data, sa)
                except socket.error:
                    # if we hit an interface without ipv6 multicast,
                    # this can cause an error, skip such an interface
                    # case in point, 'lo'
                    pass
        for i4 in list_ips():
            if 'broadcast' not in i4:
                continue
            addr = i4['addr']
            bcast = i4['broadcast']
            net4.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF,
                           socket.inet_aton(addr))
            try:
                net4.sendto(data, ('239.255.255.253', 427))
            except socket.error as se:
                # On occasion, multicasting may be disabled
                # tolerate this scenario and move on
                if se.errno != 101:
                    raise
            net4.sendto(data, (bcast, 427))


def _grab_rsps(socks, rsps, interval, xidmap):
    r = None
    res = select.select(socks, (), (), interval)
    if res:
        r = res[0]
    while r:
        for s in r:
            (rsp, peer) = s.recvfrom(9000)
            refresh_neigh()
            _parse_slp_packet(rsp, peer, rsps, xidmap)
            res = select.select(socks, (), (), interval)
            if not res:
                r = None
            else:
                r = res[0]



def _parse_attrlist(attrstr):
    attribs = {}
    previousattrlen = None
    attrstr = stringify(attrstr)
    while attrstr:
        if len(attrstr) == previousattrlen:
            raise Exception('Looping in attrstr parsing')
        previousattrlen = len(attrstr)
        if attrstr[0] == '(':
            if ')' not in attrstr:
                attribs['INCOMPLETE'] = True
                return attribs
            currattr = attrstr[1:attrstr.index(')')]
            if '=' not in currattr:  # Not allegedly kosher, but still..
                attribs[currattr] = None
            else:
                attrname, attrval = currattr.split('=', 1)
                attribs[attrname] = []
                for val in attrval.split(','):
                    if val[:3] == '\\FF':  # we should make this bytes
                        finalval = bytearray([])
                        for bnum in attrval[3:].split('\\'):
                            if bnum == '':
                                continue
                            finalval.append(int(bnum, 16))
                        val = finalval
                        if 'uuid' in attrname and len(val) == 16:
                            lebytes = struct.unpack_from(
                                '<IHH', memoryview(val[:8]))
                            bebytes = struct.unpack_from(
                                '>HHI', memoryview(val[8:]))
                            val = '{0:08X}-{1:04X}-{2:04X}-{3:04X}-' \
                                  '{4:04X}{5:08X}'.format(
                                lebytes[0], lebytes[1], lebytes[2], bebytes[0],
                                bebytes[1], bebytes[2]
                            ).lower()
                    attribs[attrname].append(val)
            attrstr = attrstr[attrstr.index(')'):]
        elif attrstr[0] == ','[0]:
            attrstr = attrstr[1:]
        elif ',' in attrstr:
            currattr = attrstr[:attrstr.index(',')]
            attribs[currattr] = None
            attrstr = attrstr[attrstr.index(','):]
        else:
            currattr = attrstr
            attribs[currattr] = None
            attrstr = None
    return attribs


def _parse_attrs(data, parsed, xid=None):
    headinfo = _parse_slp_header(data)
    if xid is None:
        xid = parsed['xid']
    if headinfo['function'] != 7 or headinfo['xid'] != xid:
        return
    payload = headinfo['payload']
    if struct.unpack('!H', bytes(payload[:2]))[0] != 0:
        return
    length = struct.unpack('!H', bytes(payload[2:4]))[0]
    attrstr = bytes(payload[4:4+length])
    parsed['attributes'] = _parse_attrlist(attrstr)


def fix_info(info, handler):
    if '_attempts' not in info:
        info['_attempts'] = 10
    if info['_attempts'] == 0:
        return
    info['_attempts'] -= 1
    _add_attributes(info)
    handler(info)

def _add_attributes(parsed):
    xid = parsed.get('xid', 42)
    attrq = _generate_attr_request(parsed['services'][0], xid)
    target = None
    # prefer reaching out to an fe80 if present, to be highly robust
    # in face of network changes
    for addr in parsed['addresses']:
        if addr[0].startswith('fe80'):
            target = addr
    # however if no fe80 seen, roll with the first available address
    if not target:
        target = parsed['addresses'][0]
    if len(target) == 4:
        net = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
    else:
        net = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        net.settimeout(2.0)
        net.connect(target)
    except socket.error:
        return
    try:
        net.sendall(attrq)
        rsp = net.recv(8192)
        net.close()
        _parse_attrs(rsp, parsed, xid)
    except Exception:
        # this can be a messy area, just degrade the quality of rsp
        # in a bad situation
        return


def unicast_scan(address):
    pass

def query_srvtypes(target):
    """Query the srvtypes advertised by the target

    :param target: A sockaddr tuple (if you get the peer info)
    """
    payload = b'\x00\x00\xff\xff\x00\x07DEFAULT'
    header = _generate_slp_header(payload, False, functionid=9, xid=1)
    packet = header + payload
    if len(target) == 2:
        net = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    elif len(target) == 4:
        net = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
    else:
        raise Exception('Unrecognized target {0}'.format(repr(target)))
    tries = 3
    connected = False
    while tries and not connected:
        tries -= 1
        try:
            net.settimeout(1.0)
            net.connect(target)
            connected = True
        except socket.error:
            pass
    if not connected:
        return [u'']
    net.sendall(packet)
    rs = net.recv(8192)
    net.close()
    parsed = _parse_slp_header(rs)
    if parsed:
        payload = parsed['payload']
        if payload[:2] != '\x00\x00':
            return
        stypelen = struct.unpack('!H', bytes(payload[2:4]))[0]
        stypes = payload[4:4+stypelen].decode('utf-8')
        return stypes.split(',')

def rescan(handler):
    known_peers = set([])
    for scanned in scan():
        for addr in scanned['addresses']:
            ip = addr[0].partition('%')[0]  # discard scope if present
            if ip not in neightable:
                continue
            if addr in known_peers:
                break
            known_peers.add(addr)
        else:
            handler(scanned)


def snoop(handler, protocol=None):
    """Watch for SLP activity

    handler will be called with a dictionary of relevant attributes

    :param handler:
    :return:
    """
    try:
        active_scan(handler, protocol)
    except Exception:
        raise
    net = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
    net.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
    slpg = socket.inet_pton(socket.AF_INET6, 'ff01::123')
    slpg2 = socket.inet_pton(socket.AF_INET6, 'ff02::123')
    for i6idx in list_interface_indexes():
        mreq = slpg + struct.pack('=I', i6idx)
        net.setsockopt(IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
        mreq = slpg2 + struct.pack('=I', i6idx)
        net.setsockopt(IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
    net4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    net.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    net4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    for i4 in list_ips():
        if 'broadcast' not in i4:
            continue
        slpmcast = socket.inet_aton('239.255.255.253') + \
            socket.inet_aton(i4['addr'])
        try:
            net4.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
                            slpmcast)
        except socket.error as e:
            if e.errno != 98:
                raise
            # socket in use can occur when aliased ipv4 are encountered
    net.bind(('', 427))
    net4.bind(('', 427))

    while True:
        try:
            newmacs = set([])
            r, _, _ = select.select((net, net4), (), (), 60)
            # clear known_peers and peerbymacaddress
            # to avoid stale info getting in...
            # rely upon the select(0.2) to catch rapid fire and aggregate ip
            # addresses that come close together
            # calling code needs to understand deeper context, as snoop
            # will now yield dupe info over time
            known_peers = set([])
            peerbymacaddress = {}
            while r:
                for s in r:
                    (rsp, peer) = s.recvfrom(9000)
                    ip = peer[0].partition('%')[0]
                    if peer in known_peers:
                        continue
                    if ip not in neightable:
                        update_neigh()
                    if ip not in neightable:
                        continue
                    known_peers.add(peer)
                    mac = neightable[ip]
                    if mac in peerbymacaddress:
                        peerbymacaddress[mac]['addresses'].append(peer)
                    else:
                        q = query_srvtypes(peer)
                        if not q or not q[0]:
                            # SLP might have started and not ready yet
                            # ignore for now
                            known_peers.discard(peer)
                            continue
                        # we want to prioritize the very well known services
                        svcs = []
                        for svc in q:
                            if svc in _slp_services:
                                svcs.insert(0, svc)
                            else:
                                svcs.append(svc)
                        peerbymacaddress[mac] = {
                            'services': svcs,
                            'addresses': [peer],
                        }
                    newmacs.add(mac)
                r, _, _ = select.select((net, net4), (), (), 0.2)
            for mac in newmacs:
                peerbymacaddress[mac]['xid'] = 1
                _add_attributes(peerbymacaddress[mac])
                peerbymacaddress[mac]['hwaddr'] = mac
                peerbymacaddress[mac]['protocol'] = protocol
                for srvurl in peerbymacaddress[mac].get('urls', ()):
                    if len(srvurl) > 4:
                        srvurl = srvurl[:-3]
                    if srvurl.endswith('://Athena:'):
                        continue
                if 'service:ipmi' in peerbymacaddress[mac]['services']:
                    continue
                if 'service:lightttpd' in peerbymacaddress[mac]['services']:
                    currinf = peerbymacaddress[mac]
                    curratt = currinf.get('attributes', {})
                    if curratt.get('System-Manufacturing', [None])[0] == 'Lenovo' and curratt.get('type', [None])[0] == 'LenovoThinkServer':
                        peerbymacaddress[mac]['services'] = ['service:lenovo-tsm']
                    else:
                        continue
                handler(peerbymacaddress[mac])
        except Exception:
            raise


def active_scan(handler, protocol=None):
    known_peers = set([])
    for scanned in scan():
        for addr in scanned['addresses']:
            ip = addr[0].partition('%')[0]  # discard scope if present
            if ip not in neightable:
                continue
            if addr in known_peers:
                break
            known_peers.add(addr)
        else:
            scanned['protocol'] = protocol
            handler(scanned)


def scan(srvtypes=_slp_services, addresses=None, localonly=False):
    """Find targets providing matching requested srvtypes

    This is a generator that will iterate over respondants to the SrvType
    requested.

    :param srvtypes: An iterable list of the service types to find
    :param addresses: An iterable of addresses/ranges.  Default is to scan
                      local network segment using multicast and broadcast.
                      Each address can be a single address, hyphen-delimited
                      range, or an IP/CIDR indication of a network.
    :return: Iterable set of results
    """
    net = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
    net4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # increase RCVBUF to max, mitigate chance of
    # failure due to full buffer.
    net.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 16777216)
    net4.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 16777216)
    # SLP is very poor at scanning large counts and managing it, so we
    # must make the best of it
    # Some platforms/config default to IPV6ONLY, we are doing IPv4
    # too, so force it
    #net.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
    # we are going to do broadcast, so allow that...
    initxid = random.randint(0, 32768)
    xididx = 0
    xidmap = {}
    # First we give fast repsonders of each srvtype individual chances to be
    # processed, mitigating volume of response traffic
    rsps = {}
    for srvtype in srvtypes:
        xididx += 1
        _find_srvtype(net, net4, srvtype, addresses, initxid + xididx)
        xidmap[initxid + xididx] = srvtype
        _grab_rsps((net, net4), rsps, 0.1, xidmap)
        # now do a more slow check to work to get stragglers,
        # but fortunately the above should have taken the brunt of volume, so
        # reduced chance of many responses overwhelming receive buffer.
    _grab_rsps((net, net4), rsps, 1, xidmap)
    # now to analyze and flesh out the responses
    for id in rsps:
        for srvurl in rsps[id].get('urls', ()):
            if len(srvurl) > 4:
                srvurl = srvurl[:-3]
            if srvurl.endswith('://Athena:'):
                continue
        if 'service:ipmi' in rsps[id]['services']:
            continue
        if localonly:
            for addr in rsps[id]['addresses']:
                if 'fe80' in addr[0]:
                    break
            else:
                continue
        _add_attributes(rsps[id])
        if 'service:lighttpd' in rsps[id]['services']:
            currinf = rsps[id]
            curratt = currinf.get('attributes', {})
            if curratt.get('System-Manufacturing', [None])[0] == 'Lenovo' and curratt.get('type', [None])[0] == 'LenovoThinkServer':
               currinf['services'] = ['service:lenovo-tsm']
               serialnumber = curratt.get('Product-Serial', curratt.get('SerialNumber', None))
               if serialnumber:
                   curratt['enclosure-serial-number'] = serialnumber
               mtm = curratt.get('Machine-Type', curratt.get('Product-Name', None))
               if mtm:
                   mtm[0] = mtm[0].rstrip()
                   curratt['enclosure-machinetype-model'] = mtm
            else:
                continue
        del rsps[id]['payload']
        del rsps[id]['function']
        del rsps[id]['xid']
        yield rsps[id]


# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2016 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# A consolidated manage of neighbor table information management.
# Ultimately, this should use AF_NETLINK, but in the interest of time,
# use ip neigh for the moment

import subprocess

neightable = {}
neightime = 0

import re

_validmac = re.compile('..:..:..:..:..:..')


def update_neigh():
    global neightable
    global neightime
    neightable = {}
    if os.name == 'nt':
        return
    ipn = subprocess.Popen(['ip', 'neigh'], stdin=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE)
    (neighdata, err) = ipn.communicate()
    neighdata = stringify(neighdata)
    for entry in neighdata.split('\n'):
        entry = entry.split(' ')
        if len(entry) < 5 or not entry[4]:
            continue
        if entry[0] in ('192.168.0.100', '192.168.70.100', '192.168.70.125'):
            # Note that these addresses are common static ip addresses
            # that are hopelessly ambiguous if there are many
            # so ignore such entries and move on
            # ideally the system network steers clear of this landmine of
            # a subnet, but just in case
            continue
        if not _validmac.match(entry[4]):
            continue
        neightable[entry[0]] = entry[4]
    neightime = os.times()[4]


def refresh_neigh():
    global neightime
    if os.name == 'nt':
        return
    if os.times()[4] > (neightime + 30):
        update_neigh()
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2014 IBM Corporation
# Copyright 2015-2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Various utility functions that do not neatly fit into one category or another
import base64
import hashlib
import netifaces
import re
import socket
import ssl

def stringify(instr):
    # Normalize unicode and bytes to 'str', correcting for
    # current python version
    if isinstance(instr, bytes) and not isinstance(instr, str):
        return instr.decode('utf-8', errors='replace')
    elif not isinstance(instr, bytes) and not isinstance(instr, str):
        return instr.encode('utf-8')
    return instr

def list_interface_indexes():
    # Getting the interface indexes in a portable manner
    # would be better, but there's difficulty from a python perspective.
    # For now be linux specific
    try:
        for iface in os.listdir('/sys/class/net/'):
            if not os.path.exists('/sys/class/net/{0}/ifindex'.format(iface)):
                continue
            ifile = open('/sys/class/net/{0}/ifindex'.format(iface), 'r')
            intidx = int(ifile.read())
            ifile.close()
            yield intidx
    except (IOError, OSError):
        # Probably situation is non-Linux, just do limited support for
        # such platforms until other people come along
        for iface in netifaces.interfaces():
            addrinfo = netifaces.ifaddresses(iface).get(socket.AF_INET6, [])
            for addr in addrinfo:
                v6addr = addr.get('addr', '').partition('%')[2]
                if v6addr:
                    yield(int(v6addr))
                    break
        return


def list_ips():
    # Used for getting addresses to indicate the multicast address
    # as well as getting all the broadcast addresses
    for iface in netifaces.interfaces():
        addrs = netifaces.ifaddresses(iface)
        if netifaces.AF_INET in addrs:
            for addr in addrs[netifaces.AF_INET]:
                yield addr

def randomstring(length=20):
    """Generate a random string of requested length

    :param length: The number of characters to produce, defaults to 20
    """
    chunksize = length // 4
    if length % 4 > 0:
        chunksize += 1
    strval = base64.urlsafe_b64encode(os.urandom(chunksize * 3))
    return stringify(strval[0:length])


def securerandomnumber(low=0, high=4294967295):
    """Return a random number within requested range

    Note that this function will not return smaller than 0 nor larger
    than 2^32-1 no matter what is requested.
    The python random number facility does not provide characteristics
    appropriate for secure rng, go to os.urandom

    :param low: Smallest number to return (defaults to 0)
    :param high: largest number to return (defaults to 2^32-1)
    """
    number = -1
    while number < low or number > high:
        number = struct.unpack("I", os.urandom(4))[0]
    return number


def monotonic_time():
    """Return a monotoc time value

    In scenarios like timeouts and such, monotonic timing is preferred.
    """
    # for now, just support POSIX systems
    return os.times()[4]


def get_certificate_from_file(certfile):
    cert = open(certfile, 'r').read()
    inpemcert = False
    prunedcert = ''
    for line in cert.split('\n'):
        if '-----BEGIN CERTIFICATE-----' in line:
            inpemcert = True
        if inpemcert:
            prunedcert += line
        if '-----END CERTIFICATE-----' in line:
            break
    return ssl.PEM_cert_to_DER_cert(prunedcert)


def get_fingerprint(certificate, algo='sha512'):
    if algo == 'sha256':
        return 'sha256$' + hashlib.sha256(certificate).hexdigest()
    elif algo == 'sha512':
        return 'sha512$' + hashlib.sha512(certificate).hexdigest()
    raise Exception('Unsupported fingerprint algorithm ' + algo)


def cert_matches(fingerprint, certificate):
    if not fingerprint or not certificate:
        return False
    algo, _, fp = fingerprint.partition('$')
    newfp = None
    if algo in ('sha512', 'sha256'):
        newfp = get_fingerprint(certificate, algo)
    return newfp and fingerprint == newfp


numregex = re.compile('([0-9]+)')

def naturalize_string(key):
    """Analyzes string in a human way to enable natural sort

    :param nodename: The node name to analyze
    :returns: A structure that can be consumed by 'sorted'
    """
    return [int(text) if text.isdigit() else text.lower()
            for text in re.split(numregex, key)]

def natural_sort(iterable):
    """Return a sort using natural sort if possible

    :param iterable:
    :return:
    """
    try:
        return sorted(iterable, key=naturalize_string)
    except TypeError:
        # The natural sort attempt failed, fallback to ascii sort
        return sorted(iterable)


if __name__ == '__main__':
    print(repr(list(scan(addresses=['10.240.52.189']))))
