|
| 1 | +Handle IPv6 |
| 2 | + |
| 3 | +Display IPv6 PIF's fields when primary_address_type is IPv6 |
| 4 | +Reconfigure management interface with appropriate method |
| 5 | +Support network reset in the IPv6 case |
| 6 | + |
| 7 | +Upstream PR: https://github.com/xapi-project/xsconsole/pull/4 |
| 8 | + |
| 9 | +diff --git a/XSConsoleData.py b/XSConsoleData.py |
| 10 | +index 829ac8d..24798f0 100644 |
| 11 | +--- a/XSConsoleData.py |
| 12 | ++++ b/XSConsoleData.py |
| 13 | +@@ -910,7 +910,21 @@ def ReconfigureManagement(self, inPIF, inMode, inIP, inNetmask, inGateway, in |
| 14 | + Auth.Inst().AssertAuthenticated() |
| 15 | + try: |
| 16 | + self.RequireSession() |
| 17 | +- self.session.xenapi.PIF.reconfigure_ip(inPIF['opaqueref'], inMode, inIP, inNetmask, inGateway, FirstValue(inDNS, '')) |
| 18 | ++ if inPIF['primary_address_type'].lower() == 'ipv4': |
| 19 | ++ self.session.xenapi.PIF.reconfigure_ip(inPIF['opaqueref'], inMode, inIP, inNetmask, inGateway, FirstValue(inDNS, '')) |
| 20 | ++ if inPIF['ipv6_configuration_mode'].lower() == 'static': |
| 21 | ++ # Update IPv6 DNS as well |
| 22 | ++ self.session.xenapi.PIF.reconfigure_ipv6( |
| 23 | ++ inPIF['opaqueref'], inPIF['ipv6_configuration_mode'], ','.join(inPIF['IPv6']), inPIF['ipv6_gateway'], FirstValue(inDNS, '') |
| 24 | ++ ) |
| 25 | ++ else: |
| 26 | ++ inIPv6 = inIP + '/' + inNetmask |
| 27 | ++ self.session.xenapi.PIF.reconfigure_ipv6(inPIF['opaqueref'], inMode, inIPv6, inGateway, FirstValue(inDNS, '')) |
| 28 | ++ if inPIF['ip_configuration_mode'].lower() == 'static': |
| 29 | ++ # Update IPv4 DNS as well |
| 30 | ++ self.session.xenapi.PIF.reconfigure_ip( |
| 31 | ++ inPIF['opaqueref'], inPIF['ip_configuration_mode'], inPIF['IP'], inPIF['netmask'], inPIF['gateway'], FirstValue(inDNS, '') |
| 32 | ++ ) |
| 33 | + self.session.xenapi.host.management_reconfigure(inPIF['opaqueref']) |
| 34 | + status, output = commands.getstatusoutput('%s host-signal-networking-change' % (Config.Inst().XECLIPath())) |
| 35 | + if status != 0: |
| 36 | +@@ -930,6 +944,7 @@ def DisableManagement(self): |
| 37 | + # Disable the PIF that the management interface was using |
| 38 | + for pif in self.derived.managementpifs([]): |
| 39 | + self.session.xenapi.PIF.reconfigure_ip(pif['opaqueref'], 'None','' ,'' ,'' ,'') |
| 40 | ++ self.session.xenapi.PIF.reconfigure_ipv6(pif['opaqueref'], 'None','' ,'' ,'') |
| 41 | + finally: |
| 42 | + # Network reconfigured so this link is potentially no longer valid |
| 43 | + self.session = Auth.Inst().CloseSession(self.session) |
| 44 | +@@ -965,10 +980,12 @@ def ManagementNetmask(self, inDefault = None): |
| 45 | + |
| 46 | + # FIXME: Address should come from API, but not available at present. For DHCP this is just a guess at the gateway address |
| 47 | + for pif in self.derived.managementpifs([]): |
| 48 | +- if pif['ip_configuration_mode'].lower().startswith('static'): |
| 49 | ++ ipv6 = pif['primary_address_type'].lower() == 'ipv6' |
| 50 | ++ configuration_mode = pif['ipv6_configuration_mode'] if ipv6 else pif['ip_configuration_mode'] |
| 51 | ++ if configuration_mode.lower().startswith('static'): |
| 52 | + # For static IP the API address is correct |
| 53 | +- retVal = pif['netmask'] |
| 54 | +- elif pif['ip_configuration_mode'].lower().startswith('dhcp'): |
| 55 | ++ retVal = pif['IPv6'][0].split('/')[1] if ipv6 else pif['netmask'] |
| 56 | ++ elif configuration_mode.lower().startswith('dhcp'): |
| 57 | + # For DHCP, find the gateway address by parsing the output from the 'route' command |
| 58 | + if 'bridge' in pif['network']: |
| 59 | + device = pif['network']['bridge'] |
| 60 | +@@ -995,10 +1012,12 @@ def ManagementGateway(self, inDefault = None): |
| 61 | + |
| 62 | + # FIXME: Address should come from API, but not available at present. For DHCP this is just a guess at the gateway address |
| 63 | + for pif in self.derived.managementpifs([]): |
| 64 | +- if pif['ip_configuration_mode'].lower().startswith('static'): |
| 65 | ++ ipv6 = pif['primary_address_type'].lower() == 'ipv6' |
| 66 | ++ configuration_mode = pif['ipv6_configuration_mode'] if ipv6 else pif['ip_configuration_mode'] |
| 67 | ++ if configuration_mode.lower().startswith('static'): |
| 68 | + # For static IP the API address is correct |
| 69 | +- retVal = pif['gateway'] |
| 70 | +- elif pif['ip_configuration_mode'].lower().startswith('dhcp'): |
| 71 | ++ retVal = pif['ipv6_gateway'] if ipv6 else pif['gateway'] |
| 72 | ++ elif configuration_mode.lower().startswith('dhcp'): |
| 73 | + # For DHCP, find the gateway address by parsing the output from the 'route' command |
| 74 | + if 'bridge' in pif['network']: |
| 75 | + device = pif['network']['bridge'] |
| 76 | +diff --git a/XSConsoleUtils.py b/XSConsoleUtils.py |
| 77 | +index f5a3ad1..271256b 100644 |
| 78 | +--- a/XSConsoleUtils.py |
| 79 | ++++ b/XSConsoleUtils.py |
| 80 | +@@ -190,26 +190,13 @@ def DateTimeToSecs(cls, inDateTime): |
| 81 | + class IPUtils: |
| 82 | + @classmethod |
| 83 | + def ValidateIP(cls, text): |
| 84 | +- rc = re.match("^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$", text) |
| 85 | +- if not rc: return False |
| 86 | +- ints = map(int, rc.groups()) |
| 87 | +- largest = 0 |
| 88 | +- for i in ints: |
| 89 | +- if i > 255: return False |
| 90 | +- largest = max(largest, i) |
| 91 | +- if largest is 0: return False |
| 92 | +- return True |
| 93 | ++ 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}' |
| 94 | ++ 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]))' |
| 95 | ++ return re.match(ipv4_re, text) or re.match(ipv6_re, text) |
| 96 | + |
| 97 | + @classmethod |
| 98 | + def ValidateNetmask(cls, text): |
| 99 | +- rc = re.match("^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$", text) |
| 100 | +- if not rc: |
| 101 | +- return False |
| 102 | +- ints = map(int, rc.groups()) |
| 103 | +- for i in ints: |
| 104 | +- if i > 255: |
| 105 | +- return False |
| 106 | +- return True |
| 107 | ++ return cls.ValidateIP(text) or (int(text) > 4 and int(text) < 128) |
| 108 | + |
| 109 | + @classmethod |
| 110 | + def AssertValidNetmask(cls, inIP): |
| 111 | +diff --git a/plugins-base/XSFeatureDNS.py b/plugins-base/XSFeatureDNS.py |
| 112 | +index 132b209..7840da3 100644 |
| 113 | +--- a/plugins-base/XSFeatureDNS.py |
| 114 | ++++ b/plugins-base/XSFeatureDNS.py |
| 115 | +@@ -179,7 +179,9 @@ def StatusUpdateHandler(cls, inPane): |
| 116 | + inPane.AddWrappedTextField(str(dns)) |
| 117 | + inPane.NewLine() |
| 118 | + for pif in data.derived.managementpifs([]): |
| 119 | +- if pif['ip_configuration_mode'].lower().startswith('static'): |
| 120 | ++ ipv6 = pif['primary_address_type'].lower() == 'ipv6' |
| 121 | ++ configuration_mode = pif['ipv6_configuration_mode'] if ipv6 else pif['ip_configuration_mode'] |
| 122 | ++ if configuration_mode.lower().startswith('static'): |
| 123 | + inPane.AddKeyHelpField( { Lang("Enter") : Lang("Update DNS Servers") }) |
| 124 | + break |
| 125 | + inPane.AddKeyHelpField( { |
| 126 | +@@ -203,7 +205,9 @@ def Register(self): |
| 127 | + def ActivateHandler(cls): |
| 128 | + data = Data.Inst() |
| 129 | + for pif in data.derived.managementpifs([]): |
| 130 | +- if pif['ip_configuration_mode'].lower().startswith('static'): |
| 131 | ++ ipv6 = pif['primary_address_type'].lower() == 'ipv6' |
| 132 | ++ configuration_mode = pif['ipv6_configuration_mode'] if ipv6 else pif['ip_configuration_mode'] |
| 133 | ++ if configuration_mode.lower().startswith('static'): |
| 134 | + DialogueUtils.AuthenticatedOnly(lambda: Layout.Inst().PushDialogue(DNSDialogue())) |
| 135 | + return |
| 136 | + |
| 137 | +diff --git a/plugins-base/XSFeatureInterface.py b/plugins-base/XSFeatureInterface.py |
| 138 | +index 6ea60bc..7091dfc 100644 |
| 139 | +--- a/plugins-base/XSFeatureInterface.py |
| 140 | ++++ b/plugins-base/XSFeatureInterface.py |
| 141 | +@@ -83,11 +83,17 @@ def __init__(self): |
| 142 | + self.hostname = data.host.hostname('') |
| 143 | + |
| 144 | + if currentPIF is not None: |
| 145 | +- if 'ip_configuration_mode' in currentPIF: self.mode = currentPIF['ip_configuration_mode'] |
| 146 | ++ ipv6 = currentPIF['primary_address_type'].lower() == 'ipv6' |
| 147 | ++ configuration_mode_key = 'ipv6_configuration_mode' if ipv6 else 'ip_configuration_mode' |
| 148 | ++ if configuration_mode_key in currentPIF: |
| 149 | ++ self.mode = currentPIF[configuration_mode_key] |
| 150 | + if self.mode.lower().startswith('static'): |
| 151 | +- if 'IP' in currentPIF: self.IP = currentPIF['IP'] |
| 152 | +- if 'netmask' in currentPIF: self.netmask = currentPIF['netmask'] |
| 153 | +- if 'gateway' in currentPIF: self.gateway = currentPIF['gateway'] |
| 154 | ++ if 'IP' in currentPIF: |
| 155 | ++ self.IP = currentPIF['IPv6'][0].split('/')[0] if ipv6 else currentPIF['IP'] |
| 156 | ++ if 'netmask' in currentPIF: |
| 157 | ++ self.netmask = currentPIF['IPv6'][0].split('/')[1] if ipv6 else currentPIF['netmask'] |
| 158 | ++ if 'gateway' in currentPIF: |
| 159 | ++ self.gateway = currentPIF['ipv6_gateway'] if ipv6 else currentPIF['gateway'] |
| 160 | + |
| 161 | + # Make the menu current choices point to our best guess of current choices |
| 162 | + if self.nic is not None: |
| 163 | +@@ -455,9 +461,11 @@ def StatusUpdateHandler(cls, inPane): |
| 164 | + inPane.AddWrappedTextField(Lang("<No interface configured>")) |
| 165 | + else: |
| 166 | + for pif in data.derived.managementpifs([]): |
| 167 | ++ ipv6 = pif['primary_address_type'].lower() == 'ipv6' |
| 168 | ++ configuration_mode = pif['ipv6_configuration_mode'] if ipv6 else pif['ip_configuration_mode'] |
| 169 | + inPane.AddStatusField(Lang('Device', 16), pif['device']) |
| 170 | + inPane.AddStatusField(Lang('MAC Address', 16), pif['MAC']) |
| 171 | +- inPane.AddStatusField(Lang('DHCP/Static IP', 16), pif['ip_configuration_mode']) |
| 172 | ++ inPane.AddStatusField(Lang('DHCP/Static IP', 16), configuration_mode) |
| 173 | + |
| 174 | + inPane.AddStatusField(Lang('IP address', 16), data.ManagementIP('')) |
| 175 | + inPane.AddStatusField(Lang('Netmask', 16), data.ManagementNetmask('')) |
| 176 | +diff --git a/plugins-base/XSFeatureNetworkReset.py b/plugins-base/XSFeatureNetworkReset.py |
| 177 | +index b20a08c..38d69d3 100644 |
| 178 | +--- a/plugins-base/XSFeatureNetworkReset.py |
| 179 | ++++ b/plugins-base/XSFeatureNetworkReset.py |
| 180 | +@@ -365,18 +365,23 @@ def Commit(self): |
| 181 | + inventory['CURRENT_INTERFACES'] = '' |
| 182 | + write_inventory(inventory) |
| 183 | + |
| 184 | ++ ipv6 = self.IP.find(':') > -1 |
| 185 | ++ |
| 186 | + # Rewrite firstboot management.conf file, which will be picked it by xcp-networkd on restart (if used) |
| 187 | + f = open(management_conf, 'w') |
| 188 | + try: |
| 189 | + f.write("LABEL='" + self.device + "'\n") |
| 190 | +- f.write("MODE='" + self.mode + "'\n") |
| 191 | ++ f.write(("MODEV6" if ipv6 else "MODE") + "='" + self.mode + "'\n") |
| 192 | + if self.vlan != '': |
| 193 | + f.write("VLAN='" + self.vlan + "'\n") |
| 194 | + if self.mode == 'static': |
| 195 | +- f.write("IP='" + self.IP + "'\n") |
| 196 | +- f.write("NETMASK='" + self.netmask + "'\n") |
| 197 | ++ if ipv6: |
| 198 | ++ f.write("IPv6='" + self.IP + "/" + self.netmask + "'\n") |
| 199 | ++ else: |
| 200 | ++ f.write("IP='" + self.IP + "'\n") |
| 201 | ++ f.write("NETMASK='" + self.netmask + "'\n") |
| 202 | + if self.gateway != '': |
| 203 | +- f.write("GATEWAY='" + self.gateway + "'\n") |
| 204 | ++ f.write(("IPv6_GATEWAY" if ipv6 else "GATEWAY") + "='" + self.gateway + "'\n") |
| 205 | + if self.dns != '': |
| 206 | + f.write("DNS='" + self.dns + "'\n") |
| 207 | + finally: |
| 208 | +@@ -386,14 +391,17 @@ def Commit(self): |
| 209 | + f = open(network_reset, 'w') |
| 210 | + try: |
| 211 | + f.write('DEVICE=' + self.device + '\n') |
| 212 | +- f.write('MODE=' + self.mode + '\n') |
| 213 | ++ f.write(('MODE_V6' if ipv6 else 'MODE') + '=' + self.mode + '\n') |
| 214 | + if self.vlan != '': |
| 215 | + f.write('VLAN=' + self.vlan + '\n') |
| 216 | + if self.mode == 'static': |
| 217 | +- f.write('IP=' + self.IP + '\n') |
| 218 | +- f.write('NETMASK=' + self.netmask + '\n') |
| 219 | ++ if ipv6: |
| 220 | ++ f.write('IPV6=' + self.IP + '/' + self.netmask + '\n') |
| 221 | ++ else: |
| 222 | ++ f.write('IP=' + self.IP + '\n') |
| 223 | ++ f.write('NETMASK=' + self.netmask + '\n') |
| 224 | + if self.gateway != '': |
| 225 | +- f.write('GATEWAY=' + self.gateway + '\n') |
| 226 | ++ f.write(('GATEWAY_V6' if ipv6 else 'GATEWAY') + '=' + self.gateway + '\n') |
| 227 | + if self.dns != '': |
| 228 | + f.write('DNS=' + self.dns + '\n') |
| 229 | + finally: |
| 230 | +diff --git a/plugins-base/XSMenuLayout.py b/plugins-base/XSMenuLayout.py |
| 231 | +index e284ee9..0899446 100644 |
| 232 | +--- a/plugins-base/XSMenuLayout.py |
| 233 | ++++ b/plugins-base/XSMenuLayout.py |
| 234 | +@@ -68,9 +68,11 @@ def UpdateFieldsNETWORK(self, inPane): |
| 235 | + ntpState = 'Disabled' |
| 236 | + |
| 237 | + for pif in data.derived.managementpifs([]): |
| 238 | ++ ipv6 = pif['primary_address_type'].lower() == 'ipv6' |
| 239 | ++ configuration_mode = pif['ipv6_configuration_mode'] if ipv6 else pif['ip_configuration_mode'] |
| 240 | + inPane.AddStatusField(Lang('Device', 16), pif['device']) |
| 241 | + inPane.AddStatusField(Lang('MAC Address', 16), pif['MAC']) |
| 242 | +- inPane.AddStatusField(Lang('DHCP/Static IP', 16), pif['ip_configuration_mode']) |
| 243 | ++ inPane.AddStatusField(Lang('DHCP/Static IP', 16), configuration_mode) |
| 244 | + |
| 245 | + inPane.AddStatusField(Lang('IP address', 16), data.ManagementIP('')) |
| 246 | + inPane.AddStatusField(Lang('Netmask', 16), data.ManagementNetmask('')) |
0 commit comments