diff --git a/confluent_server/confluent/discovery/protocols/pxe.py b/confluent_server/confluent/discovery/protocols/pxe.py index a7b8ea5e..70f2d6a3 100644 --- a/confluent_server/confluent/discovery/protocols/pxe.py +++ b/confluent_server/confluent/discovery/protocols/pxe.py @@ -38,6 +38,7 @@ import netifaces import struct import time import traceback +import uuid libc = ctypes.CDLL(ctypes.util.find_library('c')) @@ -529,19 +530,87 @@ def check_reply(node, info, packet, sock, cfg, reqview, addr): if not httpboot: log.log({'info': 'IPv6 PXE boot attempt by {0}, but IPv6 PXE is not supported, try IPv6 HTTP boot or IPv4 boot'.format(node)}) return - return reply_dhcp6(node, addr, cfg) + return reply_dhcp6(node, addr, cfg, packet, cfd, profile, sock) else: return reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile) -def reply_dhcp6(node, addr, cfg): +def reply_dhcp6(node, addr, cfg, packet, cfd, profile, sock): myaddrs = netutil.get_my_addresses(addr[-1], socket.AF_INET6) if not myaddrs: - log.log({'info': 'Unable to provide IPv6 boot services to {0} , no viable IPv6 configuration on interface index "{1}" to respond through.'.format(node, addr[-1])}) + log.log({'info': 'Unable to provide IPv6 boot services to {0}, no viable IPv6 configuration on interface index "{1}" to respond through.'.format(node, addr[-1])}) return niccfg = netutil.get_nic_config(cfg, node, ifidx=addr[-1]) + ipv6addr = niccfg.get('ipv6_address', None) + ipv6prefix = niccfg.get('ipv6_prefix', None) + ipv6method = niccfg.get('ipv6_method', 'static') + ipv6srvaddr = niccfg.get('deploy_server_v6', None) + if not ipv6srvaddr: + log.log({'info': 'Unable to determine an appropriate ipv6 server ip for {}'.format(node)}) + return + insecuremode = cfd.get(node, {}).get('deployment.useinsecureprotocols', + {}).get('value', 'never') + if not insecuremode: + insecuremode = 'never' + proto = 'https' if insecuremode == 'never' else 'http' + bootfile = '{0}://[{1}]/confluent-public/os/{2}/boot.img'.format( + proto, ipv6srvaddr, profile + ) + if not isinstance(bootfile, bytes): + bootfile = bootfile.encode('utf8') + ipass = [] + if ipv6method not in ('dhcp', 'firmwaredhcp') and ipv6addr: + if not ipv6prefix: + log.log({'info': 'Unable to determine prefix to serve to address {} for node {}'.format(ipv6addr, node)}) + return + ipass = bytearray(40) + ipass[:4] = packet[3][:4] # pass iaid back + ipass[4:16] = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x18' + ipass[16:32] = socket.inet_pton(socket.AF_INET6, ipv6addr) + ipass[32:40] = b'\x00\x00\x00\x78\x00\x00\x01\x2c' + #1 msgtype + #3 txid + #22 - server ident + #len(packet[1]) + 4 - client ident + #len(ipass) + 4 or 0 + #len(url) + 4 + replylen = 50 + len(bootfile) + len(packet[1]) + 4 + if len(ipass): + replylen += len(ipass) + reply = bytearray(replylen) + reply[0] = 2 + reply[1:4] = packet['txid'] + offset = 4 + struct.pack_into('!HH', reply, offset, 1, len(packet[1])) + offset += 4 + reply[offset:offset+len(packet[1])] = packet[1] + offset += len(packet[1]) + struct.pack_into('!HHH', reply, offset, 2, 18, 4) + offset += 6 + reply[offset:offset+16] = get_my_duid() + offset += 16 + if ipass: + struct.pack_into('!HH', reply, offset, 3, len(ipass)) + offset += 4 + reply[offset:offset + len(ipass)] = ipass + offset += len(ipass) + struct.pack_into('!HH', reply, offset, 59, len(bootfile)) + offset += 4 + reply[offset:offset + len(bootfile)] = bootfile + offset += len(bootfile) + # Need the HTTPClient in the vendor class for reply + struct.pack_into('!HHIH', reply, offset, 16, 16, 0, 10) + offset += 10 + reply[offset:offset + 10] = b'HTTPClient' + sock.sendto(reply, addr) + + +_myuuid = None +def get_my_duid(): + global _myuuid + if not _myuuid: + _myuuid = uuid.uuid4().bytes + return _myuuid - print(repr(addr)) - pass def reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile): replen = 275 # default is going to be 286 diff --git a/confluent_server/confluent/netutil.py b/confluent_server/confluent/netutil.py index 3150eae8..7136600d 100644 --- a/confluent_server/confluent/netutil.py +++ b/confluent_server/confluent/netutil.py @@ -226,10 +226,13 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None, ipbynodename = None ip6bynodename = None try: - for addr in socket.getaddrinfo(node, 0, 0, socket.SOCK_DGRAM): - if addr[0] == socket.AF_INET: - ipbynodename = addr[-1][0] - elif addr[0] == socket.AF_INET6: + for addr in socket.getaddrinfo(node, 0, socket.AF_INET, socket.SOCK_DGRAM): + ipbynodename = addr[-1][0] + + except socket.gaierror: + pass + try: + for addr in socket.getaddrinfo(node, 0, socket.AF_INET6, socket.SOCK_DGRAM): ip6bynodename = addr[-1][0] except socket.gaierror: pass @@ -270,7 +273,7 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None, if nver == '4': cfgdata['prefix'] = prefix else: - cfgdata['prefix_v{}'.format(nver)] = prefix + cfgdata['ipv{}_prefix'.format(nver)] = prefix if candip in (ipbynodename, ip6bynodename): cfgdata['matchesnodename'] = True return cfgdata @@ -297,6 +300,8 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None, else: bynodename = ip6bynodename nver = '6' + if not bynodename: # node is missing either ipv6 or ipv4, ignore + continue ipbynodenamn = socket.inet_pton(fam, bynodename) if ipn_on_same_subnet(fam, svrip, ipbynodenamn, prefix): cfgdata['matchesnodename'] = True @@ -305,13 +310,15 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None, if nver == '4': cfgdata['prefix'] = prefix else: - cfgdata['prefix_v6'] = prefix + cfgdata['ipv{}_prefix'.format(nver)] = prefix for svr in candsrvs: fam, svr, prefix = svr if fam == socket.AF_INET: bynodename = ipbynodename elif fam == socket.AF_INET6: bynodename = ip6bynodename + if not bynodename: + continue # ignore undefined family bynodenamn = socket.inet_pton(fam, bynodename) if ipn_on_same_subnet(fam, svr, bynodenamn, prefix): svrname = socket.inet_ntop(fam, svr) @@ -338,8 +345,8 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None, cfgdata['prefix'] = prefix nver = '4' else: - cfgdata['prefix_v6'] = prefix nver = '6' + cfgdata['ipv{}_prefix'.format(nver)] = prefix for setting in nodenetattribs: if 'ipv{}_gateway'.format(nver) not in setting: continue