From 1b3231377c06f9d89f79c1b67ec4b4e9daf10096 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 25 Aug 2021 17:58:36 -0400 Subject: [PATCH] Draft work to update pxe and netutil for ipv6 support --- .../confluent/discovery/protocols/pxe.py | 56 ++++++++++++---- confluent_server/confluent/netutil.py | 66 +++++++++++++++++-- 2 files changed, 103 insertions(+), 19 deletions(-) diff --git a/confluent_server/confluent/discovery/protocols/pxe.py b/confluent_server/confluent/discovery/protocols/pxe.py index f49f1822..a7b8ea5e 100644 --- a/confluent_server/confluent/discovery/protocols/pxe.py +++ b/confluent_server/confluent/discovery/protocols/pxe.py @@ -25,6 +25,7 @@ import confluent.config.configmanager as cfm import confluent.collective.manager as collective import confluent.noderange as noderange +import confluent.neighutil as neighutil import confluent.log as log import confluent.netutil as netutil import confluent.util as util @@ -397,20 +398,32 @@ def snoop(handler, protocol=None, nodeguess=None): rqv = memoryview(pkt) rq = bytearray(rqv[:2]) if rq[0] == 1: # dhcpv6 solicit - process_dhcp6req(rqv, addr[0]) + process_dhcp6req(handler, rqv, addr, netc, cfg, nodeguess) except Exception as e: tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event, event=log.Events.stacktrace) -def process_dhcp6req(rqv, addr): - txid = rqv[1:4] +def process_dhcp6req(handler, rqv, addr, net, cfg, nodeguess): + ip = addr[0] req, disco = v6opts_to_dict(bytearray(rqv[4:])) - if 'uuid' not in disco: + req['txid'] = rqv[1:4] + if not disco.get('uuid', None) or not disco.get('arch', None): return - print(addr) - print(repr(disco)) - + mac = neighutil.get_hwaddr(ip.split('%', 1)[0]) + if not mac: + net.sendto(b'\x00', addr) + tries = 5 + while tries and not mac: + eventlet.sleep(0.01) + tries -= 1 + mac = neighutil.get_hwaddr(ip.split('%', 1)[0]) + info = {'hwaddr': mac, 'uuid': disco['uuid'], + 'architecture': disco['arch'], 'services': ('pxe-client',)} + if ignoredisco.get(mac, 0) + 90 < time.time(): + ignoredisco[mac] = time.time() + handler(info) + consider_discover(info, req, net, cfg, None, nodeguess, addr) def process_dhcp4req(handler, nodeguess, cfg, net4, idx, recv, rqv): rq = bytearray(rqv) @@ -501,9 +514,8 @@ def get_deployment_profile(node, cfg, cfd=None): staticassigns = {} myipbypeer = {} -def check_reply(node, info, packet, sock, cfg, reqview): +def check_reply(node, info, packet, sock, cfg, reqview, addr): httpboot = info['architecture'] == 'uefi-httpboot' - replen = 275 # default is going to be 286 cfd = cfg.get_node_attributes(node, ('deployment.*')) profile = get_deployment_profile(node, cfg, cfd) if not profile: @@ -513,6 +525,26 @@ def check_reply(node, info, packet, sock, cfg, reqview): node, info['uuid'], info['hwaddr'] )}) return + if 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) + else: + return reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile) + +def reply_dhcp6(node, addr, cfg): + 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])}) + return + niccfg = netutil.get_nic_config(cfg, node, ifidx=addr[-1]) + + print(repr(addr)) + pass + +def reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile): + replen = 275 # default is going to be 286 myipn = info['netinfo']['recvip'] myipn = socket.inet_aton(myipn) @@ -686,11 +718,11 @@ def ack_request(pkt, rq, info): repview[26:28] = struct.pack('!H', datasum) send_raw_packet(repview, len(rply), rq, info) -def consider_discover(info, packet, sock, cfg, reqview, nodeguess): +def consider_discover(info, packet, sock, cfg, reqview, nodeguess, addr=None): if info.get('hwaddr', None) in macmap and info.get('uuid', None): - check_reply(macmap[info['hwaddr']], info, packet, sock, cfg, reqview) + check_reply(macmap[info['hwaddr']], info, packet, sock, cfg, reqview, addr) elif info.get('uuid', None) in uuidmap: - check_reply(uuidmap[info['uuid']], info, packet, sock, cfg, reqview) + check_reply(uuidmap[info['uuid']], info, packet, sock, cfg, reqview, addr) elif packet.get(53, None) == b'\x03': ack_request(packet, reqview, info) elif info.get('uuid', None) and info.get('hwaddr', None): diff --git a/confluent_server/confluent/netutil.py b/confluent_server/confluent/netutil.py index f1808144..4bb341a3 100644 --- a/confluent_server/confluent/netutil.py +++ b/confluent_server/confluent/netutil.py @@ -172,20 +172,38 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None, None) cfgdata = { 'ipv4_gateway': None, + 'ipv4_broken': True, + 'ipv6_broken': True, 'ipv4_address': None, 'ipv4_method': None, 'prefix': None, + 'ipv6_prefix': None, + 'ipv6_address': None, + 'ipv6_method': None, } - nets = None - needsvrip = False + myaddrs = [] if ifidx is not None: dhcprequested = False - nets = list(_iftonets(ifidx)) - if not nets: - cfgdata['ipv4_broken'] = True + myaddrs = get_my_addresses(ifidx) + for addr in myaddrs: + try: + if addr[0] == socket.AF_INET: + del cfgdata['ipv4_broken'] + elif addr[1] == socket.AF_INET6: + del cfgdata['ipv6_broken'] + except KeyError: + pass if serverip is not None: - needsvrip = True + if '.' in serverip: + fam = socket.AF_INET + elif ':' in serverip: + fam = socket.AF_INET6 + else: + raise ValueError('"{0}" is not a valid ip argument') + ipbytes = socket.inet_pton(fam, serverip) dhcprequested = False + myips = [x for x in get_my_addresses() if x[1] == ipbytes] + print(repr(myips)) nets = list(myiptonets(serverip)) genericmethod = 'static' ipbynodename = None @@ -194,7 +212,7 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None, node, 0, socket.AF_INET, socket.SOCK_DGRAM)[0][-1][0] except Exception: ipbynodename = None - if nets is not None: + if nets: candgws = [] candsrvs = [] for net in nets: @@ -267,6 +285,40 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None, break return cfgdata +nlhdrsz = struct.calcsize('IHHII') +ifaddrsz = struct.calcsize('BBBBI') + +def get_my_addresses(idx=0, family=0): + # RTM_GETADDR = 22 + # nlmsghdr struct: u32 len, u16 type, u16 flags, u32 seq, u32 pid + nlhdr = struct.pack('IHHII', nlhdrsz + ifaddrsz, 22, 0x301, 0, 0) + # ifaddrmsg struct: u8 family, u8 prefixlen, u8 flags, u8 scope, u32 index + ifaddrmsg = struct.pack('BBBBI', family, 0, 0, 0, idx) + s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE) + s.bind((0, 0)) + s.sendall(nlhdr + ifaddrmsg) + addrs = [] + while True: + pdata = s.recv(65536) + v = memoryview(pdata) + if struct.unpack('H', v[4:6])[0] == 3: # netlink done message + break + while len(v): + length, typ = struct.unpack('IH', v[:6]) + if typ == 20: + fam, plen, _, scope, ridx = struct.unpack('BBBBI', v[nlhdrsz:nlhdrsz+ifaddrsz]) + if (ridx == idx or not idx) and scope == 0: + rta = v[nlhdrsz+ifaddrsz:length] + while len(rta): + rtalen, rtatyp = struct.unpack('HH', rta[:4]) + if rtatyp == 1: + addrs.append((fam, rta[4:rtalen].tobytes(), plen, ridx)) + if not rtalen: + break + rta = rta[rtalen:] + v = v[length:] + return addrs + def get_prefix_len_for_ip(ip): # for now, we'll use the system route table