diff --git a/SOURCES/xsconsole-10.1.14-support-ipv6.XCP-ng.patch b/SOURCES/xsconsole-10.1.14-support-ipv6.XCP-ng.patch new file mode 100644 index 0000000..8ac4a73 --- /dev/null +++ b/SOURCES/xsconsole-10.1.14-support-ipv6.XCP-ng.patch @@ -0,0 +1,246 @@ +Handle IPv6 + +Display IPv6 PIF's fields when primary_address_type is IPv6 +Reconfigure management interface with appropriate method +Support network reset in the IPv6 case + +Upstream PR: https://github.com/xapi-project/xsconsole/pull/4 + +diff --git a/XSConsoleData.py b/XSConsoleData.py +index 829ac8d..24798f0 100644 +--- a/XSConsoleData.py ++++ b/XSConsoleData.py +@@ -910,7 +910,21 @@ def ReconfigureManagement(self, inPIF, inMode, inIP, inNetmask, inGateway, in + Auth.Inst().AssertAuthenticated() + try: + self.RequireSession() +- self.session.xenapi.PIF.reconfigure_ip(inPIF['opaqueref'], inMode, inIP, inNetmask, inGateway, FirstValue(inDNS, '')) ++ if inPIF['primary_address_type'].lower() == 'ipv4': ++ self.session.xenapi.PIF.reconfigure_ip(inPIF['opaqueref'], inMode, inIP, inNetmask, inGateway, FirstValue(inDNS, '')) ++ if inPIF['ipv6_configuration_mode'].lower() == 'static': ++ # Update IPv6 DNS as well ++ self.session.xenapi.PIF.reconfigure_ipv6( ++ inPIF['opaqueref'], inPIF['ipv6_configuration_mode'], ','.join(inPIF['IPv6']), inPIF['ipv6_gateway'], FirstValue(inDNS, '') ++ ) ++ else: ++ inIPv6 = inIP + '/' + inNetmask ++ self.session.xenapi.PIF.reconfigure_ipv6(inPIF['opaqueref'], inMode, inIPv6, inGateway, FirstValue(inDNS, '')) ++ if inPIF['ip_configuration_mode'].lower() == 'static': ++ # Update IPv4 DNS as well ++ self.session.xenapi.PIF.reconfigure_ip( ++ inPIF['opaqueref'], inPIF['ip_configuration_mode'], inPIF['IP'], inPIF['netmask'], inPIF['gateway'], FirstValue(inDNS, '') ++ ) + self.session.xenapi.host.management_reconfigure(inPIF['opaqueref']) + status, output = commands.getstatusoutput('%s host-signal-networking-change' % (Config.Inst().XECLIPath())) + if status != 0: +@@ -930,6 +944,7 @@ def DisableManagement(self): + # Disable the PIF that the management interface was using + for pif in self.derived.managementpifs([]): + self.session.xenapi.PIF.reconfigure_ip(pif['opaqueref'], 'None','' ,'' ,'' ,'') ++ self.session.xenapi.PIF.reconfigure_ipv6(pif['opaqueref'], 'None','' ,'' ,'') + finally: + # Network reconfigured so this link is potentially no longer valid + self.session = Auth.Inst().CloseSession(self.session) +@@ -965,10 +980,12 @@ def ManagementNetmask(self, inDefault = None): + + # FIXME: Address should come from API, but not available at present. For DHCP this is just a guess at the gateway address + for pif in self.derived.managementpifs([]): +- if pif['ip_configuration_mode'].lower().startswith('static'): ++ ipv6 = pif['primary_address_type'].lower() == 'ipv6' ++ configuration_mode = pif['ipv6_configuration_mode'] if ipv6 else pif['ip_configuration_mode'] ++ if configuration_mode.lower().startswith('static'): + # For static IP the API address is correct +- retVal = pif['netmask'] +- elif pif['ip_configuration_mode'].lower().startswith('dhcp'): ++ retVal = pif['IPv6'][0].split('/')[1] if ipv6 else pif['netmask'] ++ elif configuration_mode.lower().startswith('dhcp'): + # For DHCP, find the gateway address by parsing the output from the 'route' command + if 'bridge' in pif['network']: + device = pif['network']['bridge'] +@@ -995,10 +1012,12 @@ def ManagementGateway(self, inDefault = None): + + # FIXME: Address should come from API, but not available at present. For DHCP this is just a guess at the gateway address + for pif in self.derived.managementpifs([]): +- if pif['ip_configuration_mode'].lower().startswith('static'): ++ ipv6 = pif['primary_address_type'].lower() == 'ipv6' ++ configuration_mode = pif['ipv6_configuration_mode'] if ipv6 else pif['ip_configuration_mode'] ++ if configuration_mode.lower().startswith('static'): + # For static IP the API address is correct +- retVal = pif['gateway'] +- elif pif['ip_configuration_mode'].lower().startswith('dhcp'): ++ retVal = pif['ipv6_gateway'] if ipv6 else pif['gateway'] ++ elif configuration_mode.lower().startswith('dhcp'): + # For DHCP, find the gateway address by parsing the output from the 'route' command + if 'bridge' in pif['network']: + device = pif['network']['bridge'] +diff --git a/XSConsoleUtils.py b/XSConsoleUtils.py +index f5a3ad1..271256b 100644 +--- a/XSConsoleUtils.py ++++ b/XSConsoleUtils.py +@@ -190,26 +190,13 @@ def DateTimeToSecs(cls, inDateTime): + class IPUtils: + @classmethod + def ValidateIP(cls, text): +- rc = re.match("^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$", text) +- if not rc: return False +- ints = map(int, rc.groups()) +- largest = 0 +- for i in ints: +- if i > 255: return False +- largest = max(largest, i) +- if largest is 0: return False +- return True ++ ipv4_re = '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}' ++ ipv6_re = '^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))' ++ return re.match(ipv4_re, text) or re.match(ipv6_re, text) + + @classmethod + def ValidateNetmask(cls, text): +- rc = re.match("^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$", text) +- if not rc: +- return False +- ints = map(int, rc.groups()) +- for i in ints: +- if i > 255: +- return False +- return True ++ return cls.ValidateIP(text) or (int(text) > 4 and int(text) < 128) + + @classmethod + def AssertValidNetmask(cls, inIP): +diff --git a/plugins-base/XSFeatureDNS.py b/plugins-base/XSFeatureDNS.py +index 132b209..7840da3 100644 +--- a/plugins-base/XSFeatureDNS.py ++++ b/plugins-base/XSFeatureDNS.py +@@ -179,7 +179,9 @@ def StatusUpdateHandler(cls, inPane): + inPane.AddWrappedTextField(str(dns)) + inPane.NewLine() + for pif in data.derived.managementpifs([]): +- if pif['ip_configuration_mode'].lower().startswith('static'): ++ ipv6 = pif['primary_address_type'].lower() == 'ipv6' ++ configuration_mode = pif['ipv6_configuration_mode'] if ipv6 else pif['ip_configuration_mode'] ++ if configuration_mode.lower().startswith('static'): + inPane.AddKeyHelpField( { Lang("Enter") : Lang("Update DNS Servers") }) + break + inPane.AddKeyHelpField( { +@@ -203,7 +205,9 @@ def Register(self): + def ActivateHandler(cls): + data = Data.Inst() + for pif in data.derived.managementpifs([]): +- if pif['ip_configuration_mode'].lower().startswith('static'): ++ ipv6 = pif['primary_address_type'].lower() == 'ipv6' ++ configuration_mode = pif['ipv6_configuration_mode'] if ipv6 else pif['ip_configuration_mode'] ++ if configuration_mode.lower().startswith('static'): + DialogueUtils.AuthenticatedOnly(lambda: Layout.Inst().PushDialogue(DNSDialogue())) + return + +diff --git a/plugins-base/XSFeatureInterface.py b/plugins-base/XSFeatureInterface.py +index 6ea60bc..7091dfc 100644 +--- a/plugins-base/XSFeatureInterface.py ++++ b/plugins-base/XSFeatureInterface.py +@@ -83,11 +83,17 @@ def __init__(self): + self.hostname = data.host.hostname('') + + if currentPIF is not None: +- if 'ip_configuration_mode' in currentPIF: self.mode = currentPIF['ip_configuration_mode'] ++ ipv6 = currentPIF['primary_address_type'].lower() == 'ipv6' ++ configuration_mode_key = 'ipv6_configuration_mode' if ipv6 else 'ip_configuration_mode' ++ if configuration_mode_key in currentPIF: ++ self.mode = currentPIF[configuration_mode_key] + if self.mode.lower().startswith('static'): +- if 'IP' in currentPIF: self.IP = currentPIF['IP'] +- if 'netmask' in currentPIF: self.netmask = currentPIF['netmask'] +- if 'gateway' in currentPIF: self.gateway = currentPIF['gateway'] ++ if 'IP' in currentPIF: ++ self.IP = currentPIF['IPv6'][0].split('/')[0] if ipv6 else currentPIF['IP'] ++ if 'netmask' in currentPIF: ++ self.netmask = currentPIF['IPv6'][0].split('/')[1] if ipv6 else currentPIF['netmask'] ++ if 'gateway' in currentPIF: ++ self.gateway = currentPIF['ipv6_gateway'] if ipv6 else currentPIF['gateway'] + + # Make the menu current choices point to our best guess of current choices + if self.nic is not None: +@@ -455,9 +461,11 @@ def StatusUpdateHandler(cls, inPane): + inPane.AddWrappedTextField(Lang("")) + else: + for pif in data.derived.managementpifs([]): ++ ipv6 = pif['primary_address_type'].lower() == 'ipv6' ++ configuration_mode = pif['ipv6_configuration_mode'] if ipv6 else pif['ip_configuration_mode'] + inPane.AddStatusField(Lang('Device', 16), pif['device']) + inPane.AddStatusField(Lang('MAC Address', 16), pif['MAC']) +- inPane.AddStatusField(Lang('DHCP/Static IP', 16), pif['ip_configuration_mode']) ++ inPane.AddStatusField(Lang('DHCP/Static IP', 16), configuration_mode) + + inPane.AddStatusField(Lang('IP address', 16), data.ManagementIP('')) + inPane.AddStatusField(Lang('Netmask', 16), data.ManagementNetmask('')) +diff --git a/plugins-base/XSFeatureNetworkReset.py b/plugins-base/XSFeatureNetworkReset.py +index b20a08c..38d69d3 100644 +--- a/plugins-base/XSFeatureNetworkReset.py ++++ b/plugins-base/XSFeatureNetworkReset.py +@@ -365,18 +365,23 @@ def Commit(self): + inventory['CURRENT_INTERFACES'] = '' + write_inventory(inventory) + ++ ipv6 = self.IP.find(':') > -1 ++ + # Rewrite firstboot management.conf file, which will be picked it by xcp-networkd on restart (if used) + f = open(management_conf, 'w') + try: + f.write("LABEL='" + self.device + "'\n") +- f.write("MODE='" + self.mode + "'\n") ++ f.write(("MODEV6" if ipv6 else "MODE") + "='" + self.mode + "'\n") + if self.vlan != '': + f.write("VLAN='" + self.vlan + "'\n") + if self.mode == 'static': +- f.write("IP='" + self.IP + "'\n") +- f.write("NETMASK='" + self.netmask + "'\n") ++ if ipv6: ++ f.write("IPv6='" + self.IP + "/" + self.netmask + "'\n") ++ else: ++ f.write("IP='" + self.IP + "'\n") ++ f.write("NETMASK='" + self.netmask + "'\n") + if self.gateway != '': +- f.write("GATEWAY='" + self.gateway + "'\n") ++ f.write(("IPv6_GATEWAY" if ipv6 else "GATEWAY") + "='" + self.gateway + "'\n") + if self.dns != '': + f.write("DNS='" + self.dns + "'\n") + finally: +@@ -386,14 +391,17 @@ def Commit(self): + f = open(network_reset, 'w') + try: + f.write('DEVICE=' + self.device + '\n') +- f.write('MODE=' + self.mode + '\n') ++ f.write(('MODE_V6' if ipv6 else 'MODE') + '=' + self.mode + '\n') + if self.vlan != '': + f.write('VLAN=' + self.vlan + '\n') + if self.mode == 'static': +- f.write('IP=' + self.IP + '\n') +- f.write('NETMASK=' + self.netmask + '\n') ++ if ipv6: ++ f.write('IPV6=' + self.IP + '/' + self.netmask + '\n') ++ else: ++ f.write('IP=' + self.IP + '\n') ++ f.write('NETMASK=' + self.netmask + '\n') + if self.gateway != '': +- f.write('GATEWAY=' + self.gateway + '\n') ++ f.write(('GATEWAY_V6' if ipv6 else 'GATEWAY') + '=' + self.gateway + '\n') + if self.dns != '': + f.write('DNS=' + self.dns + '\n') + finally: +diff --git a/plugins-base/XSMenuLayout.py b/plugins-base/XSMenuLayout.py +index e284ee9..0899446 100644 +--- a/plugins-base/XSMenuLayout.py ++++ b/plugins-base/XSMenuLayout.py +@@ -68,9 +68,11 @@ def UpdateFieldsNETWORK(self, inPane): + ntpState = 'Disabled' + + for pif in data.derived.managementpifs([]): ++ ipv6 = pif['primary_address_type'].lower() == 'ipv6' ++ configuration_mode = pif['ipv6_configuration_mode'] if ipv6 else pif['ip_configuration_mode'] + inPane.AddStatusField(Lang('Device', 16), pif['device']) + inPane.AddStatusField(Lang('MAC Address', 16), pif['MAC']) +- inPane.AddStatusField(Lang('DHCP/Static IP', 16), pif['ip_configuration_mode']) ++ inPane.AddStatusField(Lang('DHCP/Static IP', 16), configuration_mode) + + inPane.AddStatusField(Lang('IP address', 16), data.ManagementIP('')) + inPane.AddStatusField(Lang('Netmask', 16), data.ManagementNetmask('')) diff --git a/SPECS/xsconsole.spec b/SPECS/xsconsole.spec index 8b1c03c..70abfe6 100644 --- a/SPECS/xsconsole.spec +++ b/SPECS/xsconsole.spec @@ -3,7 +3,7 @@ Summary: XCP-ng Host Configuration Console Name: xsconsole Version: 10.1.14 -Release: 1.1%{?xsrel}%{?dist} +Release: 1.2%{?xsrel}%{?dist} License: GPL2 Group: Administration/System Source0: xsconsole-10.1.14.tar.gz @@ -18,6 +18,7 @@ Requires(postun): systemd # XCP-ng patches Patch1000: xsconsole-10.1.9-rebrand-xsconsole-service.XCP-ng.patch Patch1001: xsconsole-10.1.13-define-xcp-ng-colors.XCP-ng.patch +Patch1002: xsconsole-10.1.14-support-ipv6.XCP-ng.patch %description Console tool for configuring a XCP-ng installation. @@ -54,6 +55,9 @@ Console tool for configuring a XCP-ng installation. %{_unitdir}/xsconsole.service %changelog +* Wed May 10 2023 Benjamin Reis - 10.1.14-1.2 +- Add xsconsole-10.1.14-support-ipv6.XCP-ng.patch + * Wed Dec 07 2022 Samuel Verschelde - 10.1.14-1.1 - Update from XS 8.3 pre-release updates - *** Upstream changelog ***