mirror of
				https://github.com/xcat2/confluent.git
				synced 2025-11-04 13:22:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			858 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			858 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# 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 as e:
 | 
						|
        # 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 as e:
 | 
						|
        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 as e:
 | 
						|
            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
 | 
						|
import os
 | 
						|
 | 
						|
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 os
 | 
						|
import re
 | 
						|
import socket
 | 
						|
import ssl
 | 
						|
import struct
 | 
						|
 | 
						|
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']))))
 |