From 60a00c452bd22714459660f367c3d73e686580a3 Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Sat, 11 Jul 2026 00:24:30 +0200 Subject: [PATCH] Warn on conflicting entries in confluent2hosts and confluent2dnsmasq Neither tool detected when the attribute database produces conflicting name/IP data, silently emitting the conflicts. confluent2hosts now warns when the same hostname is generated for multiple different addresses within one address family (dual-stack IPv4+IPv6 pairs stay silent), which happens naturally in -a mode when a node has several networks without distinct per-net hostnames. confluent2dnsmasq now warns when generated reservations share a hostname across different IPs, reserve the same IP more than once (dnsmasq refuses to start on a duplicate dhcp-host IP), or reuse a MAC. --- confluent_client/bin/confluent2dnsmasq | 40 +++++++++++++++++++++++++- confluent_client/bin/confluent2hosts | 9 ++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/confluent_client/bin/confluent2dnsmasq b/confluent_client/bin/confluent2dnsmasq index 488cc1ac..321972c3 100644 --- a/confluent_client/bin/confluent2dnsmasq +++ b/confluent_client/bin/confluent2dnsmasq @@ -147,6 +147,41 @@ def existing_regen_args(targetpath): +def warn_conflicts(hostentries): + """Warn about reservations that conflict with one another: the same + hostname on different IPs, one IP reserved more than once (dnsmasq + refuses to start on a duplicate dhcp-host IP), or one MAC reserved + more than once. Diagnostics only; every entry is still emitted.""" + byname = {} + byip = {} + bymac = {} + for e in hostentries: + if e['hostname']: + prev = byname.setdefault(e['hostname'], e) + if prev is not e and prev['ip'] != e['ip']: + sys.stderr.write( + "WARNING: hostname '{0}' maps to both {1} ({2}) and " + '{3} ({4})\n'.format( + e['hostname'], prev['ip'], prev['node'], + e['ip'], e['node'])) + prev = byip.setdefault(e['ip'], e) + if prev is not e: + sys.stderr.write( + 'WARNING: address {0} is reserved for both {1} ({2}) and ' + '{3} ({4}); dnsmasq refuses to start on a duplicate ' + 'dhcp-host IP address\n'.format( + e['ip'], ','.join(prev['macs']), prev['node'], + ','.join(e['macs']), e['node'])) + for mac in e['macs']: + prev = bymac.setdefault(mac.lower(), e) + if prev is not e: + sys.stderr.write( + 'WARNING: MAC {0} is reserved for both {1} ({2}) and ' + '{3} ({4})\n'.format( + mac, prev['ip'], prev['node'], + e['ip'], e['node'])) + + def main(): ap = argparse.ArgumentParser( description='Create /etc/dnsmasq.d static DHCP reservations for a ' @@ -336,7 +371,10 @@ def main(): hostentries.append({'subnetkey': subnetkey, 'macs': split_list(hwaddr), - 'ip': ip, 'hostname': hostname}) + 'ip': ip, 'hostname': hostname, + 'node': node}) + + warn_conflicts(hostentries) # Resolve optional per-subnet options from the aggregated values. def resolve(values, what, subnetkey): diff --git a/confluent_client/bin/confluent2hosts b/confluent_client/bin/confluent2hosts index d31708bb..66af0f48 100644 --- a/confluent_client/bin/confluent2hosts +++ b/confluent_client/bin/confluent2hosts @@ -33,6 +33,8 @@ class HostMerger(object): self.byip = {} self.byname = {} self.byname6 = {} + self.ipbyname = {} + self.ipbyname6 = {} self.sourcelines = [] self.targlines = [] @@ -54,14 +56,21 @@ class HostMerger(object): def add_entry(self, ip, names): targ = self.byname + ipbyname = self.ipbyname if ':' in ip: targ = self.byname6 + ipbyname = self.ipbyname6 line = '{:<39} {}'.format(ip, names) x = len(self.sourcelines) self.sourcelines.append(line) for name in names.split(): if not name: continue + if ipbyname.setdefault(name, ip) != ip: + sys.stderr.write( + 'WARNING: {0} is mapped to both {1} and {2}; all entries ' + 'will be written to /etc/hosts\n'.format( + name, ipbyname[name], ip)) targ[name] = x self.byip[ip] = x