mirror of
				https://github.com/xcat2/confluent.git
				synced 2025-11-03 21:02:36 +00:00 
			
		
		
		
	The assumption that /sys/class/net is interfaces is incorrect, when encountering entries that are not interfaces, do not mess up the call.
		
			
				
	
	
		
			326 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			326 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 | 
						|
 | 
						|
# Copyright 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.
 | 
						|
# this will implement noderange grammar
 | 
						|
 | 
						|
 | 
						|
import confluent.exceptions as exc
 | 
						|
import codecs
 | 
						|
import netifaces
 | 
						|
import struct
 | 
						|
import eventlet.green.socket as socket
 | 
						|
import eventlet.support.greendns
 | 
						|
import os
 | 
						|
getaddrinfo = eventlet.support.greendns.getaddrinfo
 | 
						|
 | 
						|
 | 
						|
def mask_to_cidr(mask):
 | 
						|
    maskn = socket.inet_pton(socket.AF_INET, mask)
 | 
						|
    maskn = struct.unpack('!I', maskn)[0]
 | 
						|
    cidr = 32
 | 
						|
    while maskn & 0b1 == 0 and cidr > 0:
 | 
						|
        cidr -= 1
 | 
						|
        maskn >>= 1
 | 
						|
    return cidr
 | 
						|
 | 
						|
def cidr_to_mask(cidr):
 | 
						|
    return socket.inet_ntop(
 | 
						|
        socket.AF_INET, struct.pack('!I', (2**32 - 1) ^ (2**(32 - cidr) - 1)))
 | 
						|
 | 
						|
def ip_on_same_subnet(first, second, prefix):
 | 
						|
    if first.startswith('::ffff:') and '.' in first:
 | 
						|
        first = first.replace('::ffff:', '')
 | 
						|
    if second.startswith('::ffff:') and '.' in second:
 | 
						|
        second = second.replace('::ffff:', '')
 | 
						|
    addrinf = socket.getaddrinfo(first, None, 0, socket.SOCK_STREAM)[0]
 | 
						|
    fam = addrinf[0]
 | 
						|
    ip = socket.inet_pton(fam, addrinf[-1][0])
 | 
						|
    ip = int(codecs.encode(bytes(ip), 'hex'), 16)
 | 
						|
    addrinf = socket.getaddrinfo(second, None, 0, socket.SOCK_STREAM)[0]
 | 
						|
    if fam != addrinf[0]:
 | 
						|
        return False
 | 
						|
    txtaddr = addrinf[-1][0].split('%')[0]
 | 
						|
    oip = socket.inet_pton(fam, txtaddr)
 | 
						|
    oip = int(codecs.encode(bytes(oip), 'hex'), 16)
 | 
						|
    if fam == socket.AF_INET:
 | 
						|
        addrlen = 32
 | 
						|
    elif fam == socket.AF_INET6:
 | 
						|
        addrlen = 128
 | 
						|
    else:
 | 
						|
        raise Exception("Unknown address family {0}".format(fam))
 | 
						|
    mask = 2 ** prefix - 1 << (addrlen - prefix)
 | 
						|
    return ip & mask == oip & mask
 | 
						|
 | 
						|
 | 
						|
def address_is_local(address):
 | 
						|
    for iface in netifaces.interfaces():
 | 
						|
        for i4 in netifaces.ifaddresses(iface).get(2, []):
 | 
						|
            cidr = mask_to_cidr(i4['netmask'])
 | 
						|
            if ip_on_same_subnet(i4['addr'], address, cidr):
 | 
						|
                return True
 | 
						|
        for i6 in netifaces.ifaddresses(iface).get(10, []):
 | 
						|
            cidr = int(i6['netmask'].split('/')[1])
 | 
						|
            laddr = i6['addr'].split('%')[0]
 | 
						|
            if ip_on_same_subnet(laddr, address, cidr):
 | 
						|
                return True
 | 
						|
    return False
 | 
						|
 | 
						|
 | 
						|
_idxtoifnamemap = {}
 | 
						|
def _rebuildidxmap():
 | 
						|
    _idxtoifnamemap.clear()
 | 
						|
    for iname in os.listdir('/sys/class/net'):
 | 
						|
        try:
 | 
						|
            ci = int(open('/sys/class/net/{0}/ifindex'.format(iname)).read())
 | 
						|
            _idxtoifnamemap[ci] = iname
 | 
						|
        except Exception:  # there may be non interface in /sys/class/net
 | 
						|
            pass
 | 
						|
 | 
						|
 | 
						|
def myiptonets(svrip):
 | 
						|
    fam = netifaces.AF_INET
 | 
						|
    if ':' in svrip:
 | 
						|
        fam = netifaces.AF_INET6
 | 
						|
    relevantnic = None
 | 
						|
    for iface in netifaces.interfaces():
 | 
						|
        for addr in netifaces.ifaddresses(iface).get(fam, []):
 | 
						|
            addr = addr.get('addr', '')
 | 
						|
            addr = addr.split('%')[0]
 | 
						|
            if addresses_match(addr, svrip):
 | 
						|
                relevantnic = iface
 | 
						|
                break
 | 
						|
        else:
 | 
						|
            continue
 | 
						|
        break
 | 
						|
    return inametonets(relevantnic)
 | 
						|
 | 
						|
 | 
						|
def _iftonets(ifidx):
 | 
						|
    if isinstance(ifidx, int):
 | 
						|
        _rebuildidxmap()
 | 
						|
        ifidx = _idxtoifnamemap.get(ifidx, None)
 | 
						|
    return inametonets(ifidx)
 | 
						|
 | 
						|
def inametonets(iname):
 | 
						|
    addrs = netifaces.ifaddresses(iname)
 | 
						|
    try:
 | 
						|
        addrs = addrs[netifaces.AF_INET]
 | 
						|
    except KeyError:
 | 
						|
        return
 | 
						|
    for addr in addrs:
 | 
						|
        ip = struct.unpack('!I', socket.inet_aton(addr['addr']))[0]
 | 
						|
        mask = struct.unpack('!I', socket.inet_aton(addr['netmask']))[0]
 | 
						|
        net = ip & mask
 | 
						|
        net = socket.inet_ntoa(struct.pack('!I', net))
 | 
						|
        yield (net, mask_to_cidr(addr['netmask']), addr['addr'])
 | 
						|
 | 
						|
# TODO(jjohnson2): have a method to arbitrate setting methods, to aid
 | 
						|
# in correct matching of net.* based on parameters, mainly for pxe
 | 
						|
# The scheme for pxe:
 | 
						|
# For one: the candidate net.* should have pxe set to true, to help
 | 
						|
# disambiguate from interfaces meant for bmc access
 | 
						|
# bmc relies upon hardwaremanagement.manager, plus we don't collect
 | 
						|
# that mac address
 | 
						|
# the ip as reported by recvmsg to match the subnet of that net.* interface
 | 
						|
# if switch and port available, that should match.
 | 
						|
def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
 | 
						|
                   serverip=None):
 | 
						|
    """Fetch network configuration parameters for a nic
 | 
						|
 | 
						|
    For a given node and interface, find and retrieve the pertinent network
 | 
						|
    configuration data.  The desired configuration can be searched
 | 
						|
    either by ip or by mac.
 | 
						|
 | 
						|
    :param configmanager: The relevant confluent.config.ConfigManager
 | 
						|
        instance.
 | 
						|
    :param node:  The name of the node
 | 
						|
    :param ip:  An IP address on the intended subnet
 | 
						|
    :param mac: The mac address of the interface
 | 
						|
    :param ifidx: The local index relevant to the network.
 | 
						|
 | 
						|
    :returns: A dict of parameters, 'ipv4_gateway', ....
 | 
						|
    """
 | 
						|
    # ip parameter *could* be the result of recvmsg with cmsg to tell
 | 
						|
    # pxe *our* ip address, or it could be the desired ip address
 | 
						|
    #TODO(jjohnson2): ip address, prefix length, mac address,
 | 
						|
    # join a bond/bridge, vlan configs, etc.
 | 
						|
    # also other nic criteria, physical location, driver and index...
 | 
						|
    nodenetattribs = configmanager.get_node_attributes(
 | 
						|
        node, 'net*').get(node, {})
 | 
						|
    cfgbyname = {}
 | 
						|
    for attrib in nodenetattribs:
 | 
						|
        segs = attrib.split('.')
 | 
						|
        if len(segs) == 2:
 | 
						|
            name = None
 | 
						|
        else:
 | 
						|
            name = segs[1]
 | 
						|
        if name not in cfgbyname:
 | 
						|
            cfgbyname[name] = {}
 | 
						|
        cfgbyname[name][segs[-1]] = nodenetattribs[attrib].get('value',
 | 
						|
                                                                None)
 | 
						|
    cfgdata = {
 | 
						|
        'ipv4_gateway': None,
 | 
						|
        'ipv4_address': None,
 | 
						|
        'ipv4_method': None,
 | 
						|
        'prefix': None,
 | 
						|
    }
 | 
						|
    nets = None
 | 
						|
    needsvrip = False
 | 
						|
    if ifidx is not None:
 | 
						|
        dhcprequested = False
 | 
						|
        nets = list(_iftonets(ifidx))
 | 
						|
        if not nets:
 | 
						|
            cfgdata['ipv4_broken'] = True
 | 
						|
    if serverip is not None:
 | 
						|
        needsvrip = True
 | 
						|
        dhcprequested = False
 | 
						|
        nets = list(myiptonets(serverip))
 | 
						|
    genericmethod = 'static'
 | 
						|
    ipbynodename = None
 | 
						|
    try:
 | 
						|
        ipbynodename = socket.getaddrinfo(
 | 
						|
            node, 0, socket.AF_INET, socket.SOCK_DGRAM)[0][-1][0]
 | 
						|
    except Exception:
 | 
						|
        ipbynodename = None
 | 
						|
    if nets is not None:
 | 
						|
        candgws = []
 | 
						|
        candsrvs = []
 | 
						|
        for net in nets:
 | 
						|
            net, prefix, svrip = net
 | 
						|
            candsrvs.append(svrip)
 | 
						|
            cfgdata['deploy_server'] = svrip
 | 
						|
            for candidate in cfgbyname:
 | 
						|
                ipmethod = cfgbyname[candidate].get('ipv4_method', 'static')
 | 
						|
                if ipmethod == 'dhcp':
 | 
						|
                    dhcprequested = True
 | 
						|
                    continue
 | 
						|
                if ipmethod == 'firmwaredhcp':
 | 
						|
                    genericmethod = ipmethod
 | 
						|
                candip = cfgbyname[candidate].get('ipv4_address', None)
 | 
						|
                if candip and '/' in candip:
 | 
						|
                    candip, candprefix = candip.split('/')
 | 
						|
                    if int(candprefix) != prefix:
 | 
						|
                        continue
 | 
						|
                candgw = cfgbyname[candidate].get('ipv4_gateway', None)
 | 
						|
                if candip:
 | 
						|
                    try:
 | 
						|
                        if ip_on_same_subnet(net, candip, prefix):
 | 
						|
                            cfgdata['ipv4_address'] = candip
 | 
						|
                            cfgdata['ipv4_method'] = ipmethod
 | 
						|
                            cfgdata['ipv4_gateway'] = cfgbyname[candidate].get(
 | 
						|
                                'ipv4_gateway', None)
 | 
						|
                            cfgdata['prefix'] = prefix
 | 
						|
                            if ipbynodename and ipbynodename == candip:
 | 
						|
                                cfgdata['matchesnodename'] = True
 | 
						|
                            return cfgdata
 | 
						|
                    except Exception as e:
 | 
						|
                        cfgdata['error_msg'] = "Error trying to evaluate net.*ipv4_address attribute value '{0}' on {1}: {2}".format(candip, node, str(e))
 | 
						|
                elif candgw:
 | 
						|
                    if ip_on_same_subnet(net, candgw, prefix):
 | 
						|
                        candgws.append(candgw)
 | 
						|
        if dhcprequested:
 | 
						|
            if not cfgdata.get('ipv4_method', None):
 | 
						|
                cfgdata['ipv4_method'] = 'dhcp'
 | 
						|
            return cfgdata
 | 
						|
        if ipbynodename == None:
 | 
						|
            return cfgdata
 | 
						|
        for net in nets:
 | 
						|
            net, prefix, svrip = net
 | 
						|
            if ip_on_same_subnet(net, ipbynodename, prefix):
 | 
						|
                cfgdata['matchesnodename'] = True
 | 
						|
                cfgdata['ipv4_address'] = ipbynodename
 | 
						|
                cfgdata['ipv4_method'] = genericmethod
 | 
						|
                cfgdata['prefix'] = prefix
 | 
						|
                break
 | 
						|
        for svr in candsrvs:
 | 
						|
            if ip_on_same_subnet(svr, ipbynodename, prefix):
 | 
						|
                cfgdata['deploy_server'] = svr
 | 
						|
                break
 | 
						|
        for gw in candgws:
 | 
						|
            if ip_on_same_subnet(gw, ipbynodename, prefix):
 | 
						|
                cfgdata['ipv4_gateway'] = gw
 | 
						|
                break
 | 
						|
        return cfgdata
 | 
						|
    if ip is not None:
 | 
						|
        prefixlen = get_prefix_len_for_ip(ip)
 | 
						|
        cfgdata['prefix'] = prefixlen
 | 
						|
        for setting in nodenetattribs:
 | 
						|
            if 'ipv4_gateway' not in setting:
 | 
						|
                continue
 | 
						|
            gw = nodenetattribs[setting].get('value', None)
 | 
						|
            if gw is None or not gw:
 | 
						|
                continue
 | 
						|
            if ip_on_same_subnet(ip, gw, prefixlen):
 | 
						|
                cfgdata['ipv4_gateway'] = gw
 | 
						|
                break
 | 
						|
    return cfgdata
 | 
						|
 | 
						|
 | 
						|
def get_prefix_len_for_ip(ip):
 | 
						|
    # for now, we'll use the system route table
 | 
						|
    # later may provide for configuration lookup to override the route
 | 
						|
    # table
 | 
						|
    ip = getaddrinfo(ip, 0, socket.AF_INET)[0][-1][0]
 | 
						|
    try:
 | 
						|
        ipn = socket.inet_aton(ip)
 | 
						|
    except socket.error:  # For now, assume 64 for ipv6
 | 
						|
        return 64
 | 
						|
    # It comes out big endian, regardless of host arch
 | 
						|
    ipn = struct.unpack('>I', ipn)[0]
 | 
						|
    rf = open('/proc/net/route')
 | 
						|
    ri = rf.read()
 | 
						|
    rf.close()
 | 
						|
    ri = ri.split('\n')[1:]
 | 
						|
    for rl in ri:
 | 
						|
        if not rl:
 | 
						|
            continue
 | 
						|
        rd = rl.split('\t')
 | 
						|
        if rd[1] == '00000000':  # default gateway, not useful for this
 | 
						|
            continue
 | 
						|
        # don't have big endian to look at, assume that it is host endian
 | 
						|
        maskn = struct.unpack('I', struct.pack('>I', int(rd[7], 16)))[0]
 | 
						|
        netn = struct.unpack('I', struct.pack('>I', int(rd[1], 16)))[0]
 | 
						|
        if ipn & maskn == netn:
 | 
						|
            nbits = 0
 | 
						|
            while maskn:
 | 
						|
                nbits += 1
 | 
						|
                maskn = maskn << 1 & 0xffffffff
 | 
						|
            return nbits
 | 
						|
    raise exc.NotImplementedException("Non local addresses not supported")
 | 
						|
 | 
						|
def addresses_match(addr1, addr2):
 | 
						|
    """Check two network addresses for similarity
 | 
						|
 | 
						|
    Is it zero padded in one place, not zero padded in another?  Is one place by name and another by IP??
 | 
						|
    Is one context getting a normal IPv4 address and another getting IPv4 in IPv6 notation?
 | 
						|
    This function examines the two given names, performing the required changes to compare them for equivalency
 | 
						|
 | 
						|
    :param addr1:
 | 
						|
    :param addr2:
 | 
						|
    :return: True if the given addresses refer to the same thing
 | 
						|
    """
 | 
						|
    for addrinfo in socket.getaddrinfo(addr1, 0, 0, socket.SOCK_STREAM):
 | 
						|
        rootaddr1 = socket.inet_pton(addrinfo[0], addrinfo[4][0])
 | 
						|
        if addrinfo[0] == socket.AF_INET6 and rootaddr1[:12] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff':
 | 
						|
            # normalize to standard IPv4
 | 
						|
            rootaddr1 = rootaddr1[-4:]
 | 
						|
        for otherinfo in socket.getaddrinfo(addr2, 0, 0, socket.SOCK_STREAM):
 | 
						|
            otheraddr = socket.inet_pton(otherinfo[0], otherinfo[4][0])
 | 
						|
            if otherinfo[0] == socket.AF_INET6 and otheraddr[:12] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff':
 | 
						|
                otheraddr = otheraddr[-4:]
 | 
						|
            if otheraddr == rootaddr1:
 | 
						|
                return True
 | 
						|
    return False
 |