Skip to content

Commit 4a109ee

Browse files
committed
Support IPv6
xapi-project/xsconsole#4 Signed-off-by: BenjiReis <[email protected]>
1 parent 7fd3757 commit 4a109ee

File tree

2 files changed

+251
-1
lines changed

2 files changed

+251
-1
lines changed
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
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(''))

SPECS/xsconsole.spec

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Summary: XCP-ng Host Configuration Console
44
Name: xsconsole
55
Version: 10.1.14
6-
Release: 1.1%{?xsrel}%{?dist}
6+
Release: 1.2%{?xsrel}%{?dist}
77
License: GPL2
88
Group: Administration/System
99
Source0: xsconsole-10.1.14.tar.gz
@@ -18,6 +18,7 @@ Requires(postun): systemd
1818
# XCP-ng patches
1919
Patch1000: xsconsole-10.1.9-rebrand-xsconsole-service.XCP-ng.patch
2020
Patch1001: xsconsole-10.1.13-define-xcp-ng-colors.XCP-ng.patch
21+
Patch1002: xsconsole-10.1.14-support-ipv6.XCP-ng.patch
2122

2223
%description
2324
Console tool for configuring a XCP-ng installation.
@@ -54,6 +55,9 @@ Console tool for configuring a XCP-ng installation.
5455
%{_unitdir}/xsconsole.service
5556

5657
%changelog
58+
* Wed May 10 2023 Benjamin Reis <[email protected]> - 10.1.14-1.2
59+
- Add xsconsole-10.1.14-support-ipv6.XCP-ng.patch
60+
5761
* Wed Dec 07 2022 Samuel Verschelde <[email protected]> - 10.1.14-1.1
5862
- Update from XS 8.3 pre-release updates
5963
- *** Upstream changelog ***

0 commit comments

Comments
 (0)