Skip to content

Commit

Permalink
Allow to install an host with IPv6 management interface
Browse files Browse the repository at this point in the history
Adds a screen to choose between IPv4, IPv6
and dual stack (with IPv4 as primary address type for management).

Write network conf file for IPv6 so that netinstall work in IPv6's modes

Configure the host with an IPv6 management interface

Signed-off-by: BenjiReis <[email protected]>
  • Loading branch information
benjamreis committed May 11, 2023
1 parent cc21554 commit 9b0f0bd
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 180 deletions.
25 changes: 15 additions & 10 deletions backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1503,15 +1503,15 @@ def configureNetworking(mounts, admin_iface, admin_bridge, admin_config, hn_conf
print >>mc, "NETMASK='%s'" % admin_config.netmask
if admin_config.gateway:
print >>mc, "GATEWAY='%s'" % admin_config.gateway
if manual_nameservers:
print >>mc, "DNS='%s'" % (','.join(nameservers),)
if domain:
print >>mc, "DOMAIN='%s'" % domain
print >>mc, "MODEV6='%s'" % netinterface.NetInterface.getModeStr(admin_config.modev6)
if admin_config.modev6 == netinterface.NetInterface.Static:
print >>mc, "IPv6='%s'" % admin_config.ipv6addr
if admin_config.ipv6_gateway:
print >>mc, "IPv6_GATEWAY='%s'" % admin_config.ipv6_gateway
if manual_nameservers:
print >>mc, "DNS='%s'" % (','.join(nameservers),)
if domain:
print >>mc, "DOMAIN='%s'" % domain
if admin_config.vlan:
print >>mc, "VLAN='%d'" % admin_config.vlan
mc.close()
Expand Down Expand Up @@ -1553,12 +1553,17 @@ def configureNetworking(mounts, admin_iface, admin_bridge, admin_config, hn_conf
# now we need to write /etc/sysconfig/network
nfd = open("%s/etc/sysconfig/network" % mounts["root"], "w")
nfd.write("NETWORKING=yes\n")
if admin_config.modev6:
nfd.write("NETWORKING_IPV6=yes\n")
util.runCmd2(['chroot', mounts['root'], 'systemctl', 'enable', 'ip6tables'])
else:
nfd.write("NETWORKING_IPV6=no\n")
netutil.disable_ipv6_module(mounts["root"])
with open("%s/etc/sysctl.d/91-net-ipv6.conf" % mounts["root"], "w") as ipv6_conf:
if admin_config.modev6:
nfd.write("NETWORKING_IPV6=yes\n")
util.runCmd2(['chroot', mounts['root'], 'systemctl', 'enable', 'ip6tables'])
for i in ['all', 'default']:
ipv6_conf.write('net.ipv6.conf.%s.disable_ipv6=0\n' % i)
else:
nfd.write("NETWORKING_IPV6=no\n")
for i in ['all', 'default']:
ipv6_conf.write('net.ipv6.conf.%s.disable_ipv6=1\n' % i)
netutil.disable_ipv6_module(mounts["root"])
nfd.write("IPV6_AUTOCONF=no\n")
nfd.write('NTPSERVERARGS="iburst prefer"\n')
nfd.close()
Expand Down
98 changes: 71 additions & 27 deletions netinterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def getTextOrNone(nodelist):
rc = rc + node.data
return rc == "" and None or rc.strip().encode()

class NetInterface:
class NetInterface(object):
""" Represents the configuration of a network interface. """

Static = 1
Expand All @@ -25,7 +25,7 @@ class NetInterface:

def __init__(self, mode, hwaddr, ipaddr=None, netmask=None, gateway=None,
dns=None, domain=None, vlan=None):
assert mode is None or mode == self.Static or mode == self.DHCP
assert mode in [None, self.Static, self.DHCP, self.Autoconf]
if ipaddr == '':
ipaddr = None
if netmask == '':
Expand All @@ -36,31 +36,25 @@ def __init__(self, mode, hwaddr, ipaddr=None, netmask=None, gateway=None,
dns = None
elif isinstance(dns, str):
dns = [ dns ]
if mode == self.Static:
assert ipaddr
assert netmask
is_static = mode == self.Static
if is_static:
assert ipaddr and netmask

self.mode = mode
self.hwaddr = hwaddr
if mode == self.Static:
self.ipaddr = ipaddr
self.netmask = netmask
self.gateway = gateway
self.dns = dns
self.domain = domain
else:
self.ipaddr = None
self.netmask = None
self.gateway = None
self.dns = None
self.domain = None
self.vlan = vlan

# Initialise IPv6 to None.
self.modev6 = None
self.ipv6addr = None
self.ipv6_gateway = None

self.mode = mode
self.ipaddr = ipaddr if is_static else None
self.netmask = netmask if is_static else None
self.gateway = gateway if is_static else None

self.dns = dns if is_static else None
self.domain = domain if is_static else None
self.vlan = vlan

def __repr__(self):
hw = "hwaddr = '%s' " % self.hwaddr

Expand Down Expand Up @@ -124,7 +118,10 @@ def valid(self):

def isStatic(self):
""" Returns true if a static interface configuration is represented. """
return self.mode == self.Static
return self.mode == self.Static or (self.mode == None and self.modev6 == self.Static)

def isDHCP(self):
return self.mode == self.DHCP or (self.mode == None and self.modev6 == self.DHCP)

def isVlan(self):
return self.vlan is not None
Expand All @@ -143,13 +140,12 @@ def writeDebStyleInterface(self, iface, f):

# Debian style interfaces are only used for the installer; dom0 only uses CentOS style
# IPv6 is only enabled through answerfiles and so is not supported here.
assert self.modev6 is None
assert self.mode
assert self.modev6 or self.mode
iface_vlan = self.getInterfaceName(iface)

if self.mode == self.DHCP:
f.write("iface %s inet dhcp\n" % iface_vlan)
else:
elif self.mode == self.Static:
# CA-11825: broadcast needs to be determined for non-standard networks
bcast = self.getBroadcast()
f.write("iface %s inet static\n" % iface_vlan)
Expand All @@ -160,12 +156,21 @@ def writeDebStyleInterface(self, iface, f):
if self.gateway:
f.write(" gateway %s\n" % self.gateway)

if self.modev6 == self.DHCP:
f.write("iface %s inet6 dhcp\n" % iface_vlan)
if self.modev6 == self.Autoconf:
f.write("iface %s inet6 auto\n" % iface_vlan)
elif self.modev6 == self.Static:
f.write("iface %s inet6 static\n" % iface_vlan)
f.write(" address %s\n" % self.ipv6addr)
if self.ipv6_gateway:
f.write(" gateway %s\n" % self.ipv6_gateway)

def writeRHStyleInterface(self, iface):
""" Write a RedHat-style configuration entry for this interface to
file object f using interface name iface. """

assert self.modev6 is None
assert self.mode
assert self.modev6 or self.mode
iface_vlan = self.getInterfaceName(iface)

f = open('/etc/sysconfig/network-scripts/ifcfg-%s' % iface_vlan, 'w')
Expand All @@ -175,17 +180,38 @@ def writeRHStyleInterface(self, iface):
f.write("BOOTPROTO=dhcp\n")
f.write("PERSISTENT_DHCLIENT=1\n")
else:
f.write("BOOTPROTO=none\n")

if self.mode == self.Static:
# CA-11825: broadcast needs to be determined for non-standard networks
bcast = self.getBroadcast()
f.write("BOOTPROTO=none\n")
f.write("IPADDR=%s\n" % self.ipaddr)
if bcast is not None:
f.write("BROADCAST=%s\n" % bcast)
f.write("NETMASK=%s\n" % self.netmask)
if self.gateway:
f.write("GATEWAY=%s\n" % self.gateway)

if self.modev6:
with open('/etc/sysconfig/network', 'w') as net_conf:
net_conf.write("NETWORKING_IPV6=yes\n")
f.write("IPV6INIT=yes\n")
f.write("IPV6_DEFROUTE=yes\n")
f.write("IPV6_DEFAULTDEV=%s\n" % iface_vlan)
f.write("IPV6_AUTOCONF=yes\n" if self.modev6 == self.Autoconf else "IPV6_AUTOCONF=no\n")

if self.modev6 == self.DHCP:
f.write("DHCPV6C=yes\n")
f.write("PERSISTENT_DHCLIENT_IPV6=yes\n")
f.write("IPV6_FORCE_ACCEPT_RA=yes\n")
elif self.modev6 == self.Static:
f.write("IPV6ADDR=%s\n" % self.ipv6addr)
if self.ipv6_gateway:
f.write("IPV6_DEFAULTGW=%s\n" % (self.ipv6_gateway))

if self.vlan:
f.write("VLAN=yes\n")

f.close()


Expand Down Expand Up @@ -348,3 +374,21 @@ def loadFromNetDb(jdata, hwaddr):

nic.addIPv6(modev6, ipv6addr, gatewayv6)
return nic

class NetInterfaceV6(NetInterface):
def __init__(self, mode, hwaddr, ipaddr=None, netmask=None, gateway=None, dns=None, domain=None, vlan=None):
super(NetInterfaceV6, self).__init__(None, hwaddr, None, None, None, None, None, vlan)

is_static = mode == self.Static
ipv6addr = None
if is_static:
assert ipaddr and netmask
ipv6addr = ipaddr + "/" + netmask
if dns == '':
dns = None
elif isinstance(dns, str):
dns = [ dns ]
self.dns = dns
self.domain = domain

self.addIPv6(mode, ipv6addr=ipv6addr, ipv6gw=gateway)
35 changes: 22 additions & 13 deletions netutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
import diskutil
import util
import re
import socket
import subprocess
import time
import errno
from xcp import logger
from xcp.net.biosdevname import all_devices_all_names
from socket import inet_ntoa
from struct import pack

class NIC:
Expand Down Expand Up @@ -92,7 +92,7 @@ def writeResolverFile(configuration, filename):

for iface in configuration:
settings = configuration[iface]
if settings.isStatic() and settings.dns:
if not settings.isDHCP() and settings.dns:
if settings.dns:
for server in settings.dns:
outfile.write("nameserver %s\n" % server)
Expand Down Expand Up @@ -137,7 +137,11 @@ def interfaceUp(interface):
if rc != 0:
return False
inets = filter(lambda x: x.startswith(" inet "), out.split("\n"))
return len(inets) == 1
if len(inets) == 1:
return True

inet6s = filter(lambda x: x.startswith(" inet6 "), out.split("\n"))
return len(inet6s) > 1 # Not just the fe80:: address

# work out if a link is up:
def linkUp(interface):
Expand Down Expand Up @@ -225,16 +229,21 @@ def valid_vlan(vlan):
return False
return True

def valid_ip_addr(addr):
if not re.match('^\d+\.\d+\.\d+\.\d+$', addr):
return False
els = addr.split('.')
if len(els) != 4:
def valid_ip_address_family(addr, family):
try:
socket.inet_pton(family, addr)
return True
except socket.error:
return False
for el in els:
if int(el) > 255:
return False
return True

def valid_ipv4_addr(addr):
return valid_ip_address_family(addr, socket.AF_INET)

def valid_ipv6_addr(addr):
return valid_ip_address_family(addr, socket.AF_INET6)

def valid_ip_addr(addr):
return valid_ipv4_addr(addr) or valid_ipv6_addr(addr)

def network(ipaddr, netmask):
ip = map(int,ipaddr.split('.',3))
Expand All @@ -246,7 +255,7 @@ def prefix2netmask(mask):
bits = 0
for i in xrange(32-mask, 32):
bits |= (1 << i)
return inet_ntoa(pack('>I', bits))
return socket.inet_ntoa(pack('>I', bits))

class NetDevices:
def __init__(self):
Expand Down
11 changes: 7 additions & 4 deletions tui/installer/screens.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,8 @@ def ns_callback((enabled, )):
for entry in [ns1_entry, ns2_entry, ns3_entry]:
entry.setFlags(FLAG_DISABLED, enabled)

hide_rb = answers['net-admin-configuration'].isStatic()
admin_config = answers['net-admin-configuration']
hide_rb = admin_config.valid() and not admin_config.isDHCP()

# HOSTNAME:
hn_title = Textbox(len("Hostname Configuration"), 1, "Hostname Configuration")
Expand Down Expand Up @@ -935,8 +936,9 @@ def nsvalue(answers, id):
answers['manual-nameservers'][1].append(ns2_entry.value())
if ns3_entry.value() != '':
answers['manual-nameservers'][1].append(ns3_entry.value())
if 'net-admin-configuration' in answers and answers['net-admin-configuration'].isStatic():
answers['net-admin-configuration'].dns = answers['manual-nameservers'][1]
admin_config = answers.get('net-admin-configuration')
if admin_config is not None and admin_config.valid() and not admin_config.isDHCP():
admin_config.dns = answers['manual-nameservers'][1]
else:
answers['manual-nameservers'] = (False, None)

Expand Down Expand Up @@ -1036,7 +1038,8 @@ def dhcp_change():
for x in [ ntp1_field, ntp2_field, ntp3_field ]:
x.setFlags(FLAG_DISABLED, not dhcp_cb.value())

hide_cb = answers['net-admin-configuration'].isStatic()
admin_config = answers['net-admin-configuration']
hide_cb = admin_config.valid() and not admin_config.isDHCP()

gf = GridFormHelp(tui.screen, 'NTP Configuration', 'ntpconf', 1, 4)
text = TextboxReflowed(60, "Please specify details of the NTP servers you wish to use (e.g. pool.ntp.org)?")
Expand Down
Loading

0 comments on commit 9b0f0bd

Please sign in to comment.