mirror of
https://github.com/xcat2/confluent.git
synced 2025-01-17 13:13:18 +00:00
1200f7b7a1
As we start needing to compare addresses, provide a central function to handle the various oddities associated with that.
150 lines
5.8 KiB
Python
150 lines
5.8 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 struct
|
|
import eventlet.green.socket as socket
|
|
import eventlet.support.greendns
|
|
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
|
|
|
|
|
def ip_on_same_subnet(first, second, prefix):
|
|
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
|
|
oip = socket.inet_pton(fam, addrinf[-1][0])
|
|
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
|
|
|
|
|
|
# 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):
|
|
"""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
|
|
|
|
: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*.ipv4_gateway').get(node, {})
|
|
cfgdata = {
|
|
'ipv4_gateway': None,
|
|
'prefix': None,
|
|
}
|
|
if ip is not None:
|
|
prefixlen = get_prefix_len_for_ip(ip)
|
|
cfgdata['prefix'] = prefixlen
|
|
for setting in nodenetattribs:
|
|
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
|