From 93fc6202ceda0e6ad2373e6dba52eb0588baf272 Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Tue, 30 May 2023 10:06:07 +0200 Subject: [PATCH 01/19] Write `manual_nameservers` and `domain` for IPv6 too Both fields are used for the 2 IP families and were writen in an IPv4 specific block. Signed-off-by: BenjiReis --- backend.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/backend.py b/backend.py index 1788db74..bfe9b851 100644 --- a/backend.py +++ b/backend.py @@ -1498,20 +1498,24 @@ def configureNetworking(mounts, admin_iface, admin_bridge, admin_config, hn_conf mc = open(mgmt_conf_file, 'w') print >>mc, "LABEL='%s'" % admin_iface print >>mc, "MODE='%s'" % netinterface.NetInterface.getModeStr(admin_config.mode) + static = False if admin_config.mode == netinterface.NetInterface.Static: + static = True print >>mc, "IP='%s'" % admin_config.ipaddr 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: + static = True print >>mc, "IPv6='%s'" % admin_config.ipv6addr if admin_config.ipv6_gateway: print >>mc, "IPv6_GATEWAY='%s'" % admin_config.ipv6_gateway + if static: + 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() From 685a345868f78ec17656883f3aa48da1cbbcd344 Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Thu, 1 Jun 2023 13:51:13 +0200 Subject: [PATCH 02/19] NetInterface inherits from Object Signed-off-by: BenjiReis --- netinterface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netinterface.py b/netinterface.py index 0cdcad47..af3578b8 100644 --- a/netinterface.py +++ b/netinterface.py @@ -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 From eed61bbbd79a94ebc0c3c41eedb8df38a044a75c Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Tue, 30 May 2023 10:36:16 +0200 Subject: [PATCH 03/19] Add `NetInterfaceV6` to init an IPv6 interface Inherits from `NetInterface` to mutualize the code Signed-off-by: BenjiReis --- netinterface.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/netinterface.py b/netinterface.py index af3578b8..49c0299c 100644 --- a/netinterface.py +++ b/netinterface.py @@ -348,3 +348,22 @@ 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) + + ipv6addr = None + if mode == self.Static: + assert ipaddr + assert 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) From 2ba24285ff4a57baf7055ba6974be12e23d258b3 Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Tue, 30 May 2023 10:40:33 +0200 Subject: [PATCH 04/19] isStatic helpers by IP family Signed-off-by: BenjiReis --- netinterface.py | 10 +++++++--- netutil.py | 2 +- tui/installer/screens.py | 6 +++--- tui/network.py | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/netinterface.py b/netinterface.py index 49c0299c..168387ff 100644 --- a/netinterface.py +++ b/netinterface.py @@ -122,10 +122,14 @@ def valid(self): return False return self.mode or self.modev6 - def isStatic(self): - """ Returns true if a static interface configuration is represented. """ + def isStatic4(self): + """ Returns true if an IPv4 static interface configuration is represented. """ return self.mode == self.Static + def isStatic6(self): + """ Returns true if an IPv6 static interface configuration is represented. """ + return self.modev6 == self.Static + def isVlan(self): return self.vlan is not None @@ -190,7 +194,7 @@ def writeRHStyleInterface(self, iface): def waitUntilUp(self, iface): - if not self.isStatic(): + if not self.isStatic4(): return True if not self.gateway: return True diff --git a/netutil.py b/netutil.py index 898fe0ea..c975b0d0 100644 --- a/netutil.py +++ b/netutil.py @@ -92,7 +92,7 @@ def writeResolverFile(configuration, filename): for iface in configuration: settings = configuration[iface] - if settings.isStatic() and settings.dns: + if settings.isStatic4() and settings.dns: if settings.dns: for server in settings.dns: outfile.write("nameserver %s\n" % server) diff --git a/tui/installer/screens.py b/tui/installer/screens.py index 74bb8cfb..fef4c4b4 100644 --- a/tui/installer/screens.py +++ b/tui/installer/screens.py @@ -805,7 +805,7 @@ 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() + hide_rb = answers['net-admin-configuration'].isStatic4() # HOSTNAME: hn_title = Textbox(len("Hostname Configuration"), 1, "Hostname Configuration") @@ -935,7 +935,7 @@ 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(): + if 'net-admin-configuration' in answers and answers['net-admin-configuration'].isStatic4(): answers['net-admin-configuration'].dns = answers['manual-nameservers'][1] else: answers['manual-nameservers'] = (False, None) @@ -1036,7 +1036,7 @@ 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() + hide_cb = answers['net-admin-configuration'].isStatic4() 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)?") diff --git a/tui/network.py b/tui/network.py index faf87dc9..0ab6751b 100644 --- a/tui/network.py +++ b/tui/network.py @@ -34,7 +34,7 @@ def dhcp_change(): dns_field = Entry(16) vlan_field = Entry(16) - if defaults and defaults.isStatic(): + if defaults and defaults.isStatic4(): # static configuration defined previously dhcp_rb = SingleRadioButton("Automatic configuration (DHCP)", None, 0) dhcp_rb.setCallback(dhcp_change, ()) From 44d0e58706e7bf0f4a73a0cb3534bad37390b2aa Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Tue, 30 May 2023 10:46:12 +0200 Subject: [PATCH 05/19] Use `socket.inet_pton` to validate IP Add family specific validator as well Signed-off-by: BenjiReis --- netutil.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/netutil.py b/netutil.py index c975b0d0..efa9bfe7 100644 --- a/netutil.py +++ b/netutil.py @@ -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: @@ -225,16 +225,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)) @@ -246,7 +251,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): From 6f67e14329f08885dcd70d209df1c3963025a2fa Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Tue, 30 May 2023 10:48:11 +0200 Subject: [PATCH 06/19] Take IPv6 addresses into account to determine an interface is up Signed-off-by: BenjiReis --- netutil.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/netutil.py b/netutil.py index efa9bfe7..d989edd7 100644 --- a/netutil.py +++ b/netutil.py @@ -137,7 +137,15 @@ 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( + # Filter link local addresses as well + lambda x: x.startswith(" inet6 ") and not x.startswith(" inet6 fe80::"), + out.split("\n") + ) + return len(inet6s) == 1 # work out if a link is up: def linkUp(interface): From 1210931bddfb215dbc2598e67d20b3e7b3961d11 Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Tue, 30 May 2023 10:56:47 +0200 Subject: [PATCH 07/19] Enable IPv6 in the host when it is configured Signed-off-by: BenjiReis --- backend.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend.py b/backend.py index bfe9b851..4aed43db 100644 --- a/backend.py +++ b/backend.py @@ -1557,12 +1557,18 @@ 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: + ipv6 = admin_config.modev6 is not None + if ipv6: 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: + for i in ['all', 'default']: + ipv6_conf.write('net.ipv6.conf.%s.disable_ipv6=%d\n' % (i, int(not ipv6))) + nfd.write("IPV6_AUTOCONF=no\n") nfd.write('NTPSERVERARGS="iburst prefer"\n') nfd.close() From e70aa28172c410b63ddbf363d2c1f8210a752653 Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Tue, 30 May 2023 11:16:57 +0200 Subject: [PATCH 08/19] Write IPv6 conf files for the installer Signed-off-by: BenjiReis --- netinterface.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/netinterface.py b/netinterface.py index 168387ff..fbd856f0 100644 --- a/netinterface.py +++ b/netinterface.py @@ -168,8 +168,7 @@ 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') @@ -178,7 +177,7 @@ def writeRHStyleInterface(self, iface): if self.mode == self.DHCP: f.write("BOOTPROTO=dhcp\n") f.write("PERSISTENT_DHCLIENT=1\n") - else: + elif self.mode == self.Static: # CA-11825: broadcast needs to be determined for non-standard networks bcast = self.getBroadcast() f.write("BOOTPROTO=none\n") @@ -188,6 +187,26 @@ def writeRHStyleInterface(self, iface): 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) + 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") + f.write("IPV6_AUTOCONF=no\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)) + f.write("IPV6_AUTOCONF=no\n") + elif self.modev6 == self.Autoconf: + f.write("IPV6_AUTOCONF=yes\n") + if self.vlan: f.write("VLAN=yes\n") f.close() From 7e542f17fe55c6c362ae510d969b5e8d7583fb51 Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Thu, 15 Jun 2023 09:28:51 +0200 Subject: [PATCH 09/19] Disable IP config textfield for autoconf Only static configuration should allow a user to fill the fields Signed-off-by: BenjiReis --- tui/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tui/network.py b/tui/network.py index 0ab6751b..6cb0929f 100644 --- a/tui/network.py +++ b/tui/network.py @@ -19,7 +19,7 @@ def use_vlan_cb_change(): def dhcp_change(): for x in [ ip_field, gateway_field, subnet_field, dns_field ]: - x.setFlags(FLAG_DISABLED, not dhcp_rb.selected()) + x.setFlags(FLAG_DISABLED, static_rb.selected()) gf = GridFormHelp(tui.screen, 'Networking', 'ifconfig', 1, 8) if txt is None: From e46ced82558dedd495d4ea7610d2df2ddded3444 Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Thu, 15 Jun 2023 09:42:21 +0200 Subject: [PATCH 10/19] Add a display_error helper for wainting network In case of error return directly Signed-off-by: BenjiReis --- tui/network.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/tui/network.py b/tui/network.py index 6cb0929f..338d37fe 100644 --- a/tui/network.py +++ b/tui/network.py @@ -286,23 +286,26 @@ def specify_configuration(answers, txt, defaults): ifaceName = conf_dict['config'].getInterfaceName(conf_dict['interface']) netutil.ifdown(ifaceName) - # check that we have *some* network: - if netutil.ifup(ifaceName) != 0 or not netutil.interfaceUp(ifaceName): + def display_error(): tui.progress.clearModelessDialog() tui.progress.OKDialog("Networking", "The network still does not appear to be active. Please check your settings, and try again.") - direction = REPEAT_STEP - else: - if answers and type(answers) == dict: - # write out results - answers[interface_key] = conf_dict['interface'] - answers[config_key] = conf_dict['config'] - # update cache of manual configurations - manual_config = {} - all_dhcp = False - if 'runtime-iface-configuration' in answers: - manual_config = answers['runtime-iface-configuration'][1] - manual_config[conf_dict['interface']] = conf_dict['config'] - answers['runtime-iface-configuration'] = (all_dhcp, manual_config) - tui.progress.clearModelessDialog() + return REPEAT_STEP + + # check that we have *some* network: + if netutil.ifup(ifaceName) != 0 or not netutil.interfaceUp(ifaceName): + return display_error() + + if answers and type(answers) == dict: + # write out results + answers[interface_key] = conf_dict['interface'] + answers[config_key] = conf_dict['config'] + # update cache of manual configurations + manual_config = {} + all_dhcp = False + if 'runtime-iface-configuration' in answers: + manual_config = answers['runtime-iface-configuration'][1] + manual_config[conf_dict['interface']] = conf_dict['config'] + answers['runtime-iface-configuration'] = (all_dhcp, manual_config) + tui.progress.clearModelessDialog() return direction From dea0e4440b5c9216720b74b453c1da7839ff566a Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Thu, 15 Jun 2023 09:44:44 +0200 Subject: [PATCH 11/19] Wait a bit for autoconf when checking for network Signed-off-by: BenjiReis --- tui/network.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tui/network.py b/tui/network.py index 338d37fe..fc5143fe 100644 --- a/tui/network.py +++ b/tui/network.py @@ -9,6 +9,7 @@ from netinterface import * import version import os +import time from snack import * @@ -291,8 +292,19 @@ def display_error(): tui.progress.OKDialog("Networking", "The network still does not appear to be active. Please check your settings, and try again.") return REPEAT_STEP + if netutil.ifup(ifaceName) != 0: + return display_error() + + # For Autoconf wait a bit for network setup + try_nb = 20 if conf_dict['config'].modev6 == NetInterface.Autoconf else 0 + while True: + if try_nb == 0 or netutil.interfaceUp(ifaceName): + break + try_nb -= 1 + time.sleep(0.1) + # check that we have *some* network: - if netutil.ifup(ifaceName) != 0 or not netutil.interfaceUp(ifaceName): + if not netutil.interfaceUp(ifaceName): return display_error() if answers and type(answers) == dict: From 681768f45e2ca9f02e9e2f1b4a7325e37597cab2 Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Thu, 15 Jun 2023 09:58:38 +0200 Subject: [PATCH 12/19] Put IP config info collection in a helper Signed-off-by: BenjiReis --- tui/network.py | 231 +++++++++++++++++++++++++------------------------ 1 file changed, 118 insertions(+), 113 deletions(-) diff --git a/tui/network.py b/tui/network.py index fc5143fe..29e8c0f3 100644 --- a/tui/network.py +++ b/tui/network.py @@ -14,124 +14,129 @@ from snack import * def get_iface_configuration(nic, txt=None, defaults=None, include_dns=False): - - def use_vlan_cb_change(): - vlan_field.setFlags(FLAG_DISABLED, vlan_cb.value()) - - def dhcp_change(): - for x in [ ip_field, gateway_field, subnet_field, dns_field ]: - x.setFlags(FLAG_DISABLED, static_rb.selected()) - - gf = GridFormHelp(tui.screen, 'Networking', 'ifconfig', 1, 8) - if txt is None: - txt = "Configuration for %s (%s)" % (nic.name, nic.hwaddr) - text = TextboxReflowed(45, txt) - b = [("Ok", "ok"), ("Back", "back")] - buttons = ButtonBar(tui.screen, b) - - ip_field = Entry(16) - subnet_field = Entry(16) - gateway_field = Entry(16) - dns_field = Entry(16) - vlan_field = Entry(16) - - if defaults and defaults.isStatic4(): - # static configuration defined previously - dhcp_rb = SingleRadioButton("Automatic configuration (DHCP)", None, 0) - dhcp_rb.setCallback(dhcp_change, ()) - static_rb = SingleRadioButton("Static configuration:", dhcp_rb, 1) - static_rb.setCallback(dhcp_change, ()) - if defaults.ipaddr: - ip_field.set(defaults.ipaddr) - if defaults.netmask: - subnet_field.set(defaults.netmask) - if defaults.gateway: - gateway_field.set(defaults.gateway) - if defaults.dns: - dns_field.set(defaults.dns[0]) - else: - dhcp_rb = SingleRadioButton("Automatic configuration (DHCP)", None, 1) - dhcp_rb.setCallback(dhcp_change, ()) - static_rb = SingleRadioButton("Static configuration:", dhcp_rb, 0) - static_rb.setCallback(dhcp_change, ()) - ip_field.setFlags(FLAG_DISABLED, False) - subnet_field.setFlags(FLAG_DISABLED, False) - gateway_field.setFlags(FLAG_DISABLED, False) - dns_field.setFlags(FLAG_DISABLED, False) - - vlan_cb = Checkbox("Use VLAN:", defaults.isVlan() if defaults else False) - vlan_cb.setCallback(use_vlan_cb_change, ()) - if defaults and defaults.isVlan(): - vlan_field.set(str(defaults.vlan)) - else: - vlan_field.setFlags(FLAG_DISABLED, False) - - ip_text = Textbox(15, 1, "IP Address:") - subnet_text = Textbox(15, 1, "Subnet mask:") - gateway_text = Textbox(15, 1, "Gateway:") - dns_text = Textbox(15, 1, "Nameserver:") - vlan_text = Textbox(15, 1, "VLAN (1-4094):") - - entry_grid = Grid(2, include_dns and 4 or 3) - entry_grid.setField(ip_text, 0, 0) - entry_grid.setField(ip_field, 1, 0) - entry_grid.setField(subnet_text, 0, 1) - entry_grid.setField(subnet_field, 1, 1) - entry_grid.setField(gateway_text, 0, 2) - entry_grid.setField(gateway_field, 1, 2) - if include_dns: - entry_grid.setField(dns_text, 0, 3) - entry_grid.setField(dns_field, 1, 3) - - vlan_grid = Grid(2, 1) - vlan_grid.setField(vlan_text, 0, 0) - vlan_grid.setField(vlan_field, 1, 0) - - gf.add(text, 0, 0, padding=(0, 0, 0, 1)) - gf.add(dhcp_rb, 0, 2, anchorLeft=True) - gf.add(static_rb, 0, 3, anchorLeft=True) - gf.add(entry_grid, 0, 4, padding=(0, 0, 0, 1)) - gf.add(vlan_cb, 0, 5, anchorLeft=True) - gf.add(vlan_grid, 0, 6, padding=(0, 0, 0, 1)) - gf.add(buttons, 0, 7, growx=1) - - loop = True - while loop: - result = gf.run() - - if buttons.buttonPressed(result) in ['ok', None]: - # validate input - msg = '' - if static_rb.selected(): - if not netutil.valid_ip_addr(ip_field.value()): - msg = 'IP Address' - elif not netutil.valid_ip_addr(subnet_field.value()): - msg = 'Subnet mask' - elif gateway_field.value() != '' and not netutil.valid_ip_addr(gateway_field.value()): - msg = 'Gateway' - elif dns_field.value() != '' and not netutil.valid_ip_addr(dns_field.value()): - msg = 'Nameserver' - if vlan_cb.selected(): - if not netutil.valid_vlan(vlan_field.value()): - msg = 'VLAN' - if msg != '': - tui.progress.OKDialog("Networking", "Invalid %s, please check the field and try again." % msg) + def get_ip_configuration(nic, txt, defaults, include_dns, iface_class): + def use_vlan_cb_change(): + vlan_field.setFlags(FLAG_DISABLED, vlan_cb.value()) + + def dhcp_change(): + for x in [ ip_field, gateway_field, subnet_field, dns_field ]: + x.setFlags(FLAG_DISABLED, static_rb.selected()) + + gf = GridFormHelp(tui.screen, 'Networking', 'ifconfig', 1, 8) + if txt is None: + txt = "Configuration for %s (%s)" % (nic.name, nic.hwaddr) + text = TextboxReflowed(45, txt) + b = [("Ok", "ok"), ("Back", "back")] + buttons = ButtonBar(tui.screen, b) + + ip_field = Entry(16) + subnet_field = Entry(16) + gateway_field = Entry(16) + dns_field = Entry(16) + vlan_field = Entry(16) + + if defaults and defaults.isStatic4(): + # static configuration defined previously + dhcp_rb = SingleRadioButton("Automatic configuration (DHCP)", None, 0) + dhcp_rb.setCallback(dhcp_change, ()) + static_rb = SingleRadioButton("Static configuration:", dhcp_rb, 1) + static_rb.setCallback(dhcp_change, ()) + if defaults.ipaddr: + ip_field.set(defaults.ipaddr) + if defaults.netmask: + subnet_field.set(defaults.netmask) + if defaults.gateway: + gateway_field.set(defaults.gateway) + if defaults.dns: + dns_field.set(defaults.dns[0]) + else: + dhcp_rb = SingleRadioButton("Automatic configuration (DHCP)", None, 1) + dhcp_rb.setCallback(dhcp_change, ()) + static_rb = SingleRadioButton("Static configuration:", dhcp_rb, 0) + static_rb.setCallback(dhcp_change, ()) + ip_field.setFlags(FLAG_DISABLED, False) + subnet_field.setFlags(FLAG_DISABLED, False) + gateway_field.setFlags(FLAG_DISABLED, False) + dns_field.setFlags(FLAG_DISABLED, False) + + vlan_cb = Checkbox("Use VLAN:", defaults.isVlan() if defaults else False) + vlan_cb.setCallback(use_vlan_cb_change, ()) + if defaults and defaults.isVlan(): + vlan_field.set(str(defaults.vlan)) + else: + vlan_field.setFlags(FLAG_DISABLED, False) + + ip_text = Textbox(15, 1, "IP Address:") + subnet_text = Textbox(15, 1, "Subnet mask:") + gateway_text = Textbox(15, 1, "Gateway:") + dns_text = Textbox(15, 1, "Nameserver:") + vlan_text = Textbox(15, 1, "VLAN (1-4094):") + + entry_grid = Grid(2, include_dns and 4 or 3) + entry_grid.setField(ip_text, 0, 0) + entry_grid.setField(ip_field, 1, 0) + entry_grid.setField(subnet_text, 0, 1) + entry_grid.setField(subnet_field, 1, 1) + entry_grid.setField(gateway_text, 0, 2) + entry_grid.setField(gateway_field, 1, 2) + if include_dns: + entry_grid.setField(dns_text, 0, 3) + entry_grid.setField(dns_field, 1, 3) + + vlan_grid = Grid(2, 1) + vlan_grid.setField(vlan_text, 0, 0) + vlan_grid.setField(vlan_field, 1, 0) + + gf.add(text, 0, 0, padding=(0, 0, 0, 1)) + gf.add(dhcp_rb, 0, 2, anchorLeft=True) + gf.add(static_rb, 0, 3, anchorLeft=True) + gf.add(entry_grid, 0, 4, padding=(0, 0, 0, 1)) + gf.add(vlan_cb, 0, 5, anchorLeft=True) + gf.add(vlan_grid, 0, 6, padding=(0, 0, 0, 1)) + gf.add(buttons, 0, 7, growx=1) + + loop = True + while loop: + result = gf.run() + + if buttons.buttonPressed(result) in ['ok', None]: + # validate input + msg = '' + if static_rb.selected(): + if not netutil.valid_ip_addr(ip_field.value()): + msg = 'IP Address' + elif not netutil.valid_ip_addr(subnet_field.value()): + msg = 'Subnet mask' + elif gateway_field.value() != '' and not netutil.valid_ip_addr(gateway_field.value()): + msg = 'Gateway' + elif dns_field.value() != '' and not netutil.valid_ip_addr(dns_field.value()): + msg = 'Nameserver' + if vlan_cb.selected(): + if not netutil.valid_vlan(vlan_field.value()): + msg = 'VLAN' + if msg != '': + tui.progress.OKDialog("Networking", "Invalid %s, please check the field and try again." % msg) + else: + loop = False else: loop = False - else: - loop = False - tui.screen.popWindow() + tui.screen.popWindow() - if buttons.buttonPressed(result) == 'back': return LEFT_BACKWARDS, None + if buttons.buttonPressed(result) == 'back': return LEFT_BACKWARDS, None - vlan_value = int(vlan_field.value()) if vlan_cb.selected() else None - if bool(dhcp_rb.selected()): - answers = NetInterface(NetInterface.DHCP, nic.hwaddr, vlan=vlan_value) - else: - answers = NetInterface(NetInterface.Static, nic.hwaddr, ip_field.value(), - subnet_field.value(), gateway_field.value(), - dns_field.value(), vlan=vlan_value) + vlan_value = int(vlan_field.value()) if vlan_cb.selected() else None + if bool(dhcp_rb.selected()): + answers = NetInterface(NetInterface.DHCP, nic.hwaddr, vlan=vlan_value) + else: + answers = NetInterface(NetInterface.Static, nic.hwaddr, ip_field.value(), + subnet_field.value(), gateway_field.value(), + dns_field.value(), vlan=vlan_value) + return RIGHT_FORWARDS, answers + + direction, answers = get_ip_configuration(nic, txt, defaults, include_dns, NetInterface) + if direction == LEFT_BACKWARDS: + return LEFT_BACKWARDS, None return RIGHT_FORWARDS, answers def select_netif(text, conf, offer_existing=False, default=None): From 81c0926b96e07f13813a1da1731d70c9af70c2a1 Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Thu, 15 Jun 2023 10:01:26 +0200 Subject: [PATCH 13/19] Adapt `get_iface_configuration` helper for IPv6 Signed-off-by: BenjiReis --- tui/network.py | 93 +++++++++++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/tui/network.py b/tui/network.py index 29e8c0f3..ceff83eb 100644 --- a/tui/network.py +++ b/tui/network.py @@ -10,6 +10,7 @@ import version import os import time +import socket from snack import * @@ -22,42 +23,49 @@ def dhcp_change(): for x in [ ip_field, gateway_field, subnet_field, dns_field ]: x.setFlags(FLAG_DISABLED, static_rb.selected()) - gf = GridFormHelp(tui.screen, 'Networking', 'ifconfig', 1, 8) + ipv6 = iface_class == NetInterfaceV6 + + gf = GridFormHelp(tui.screen, 'Networking', 'ifconfig', 1, 10) if txt is None: txt = "Configuration for %s (%s)" % (nic.name, nic.hwaddr) text = TextboxReflowed(45, txt) b = [("Ok", "ok"), ("Back", "back")] buttons = ButtonBar(tui.screen, b) + #TODO? Change size for IPv6? If so which size? ip_field = Entry(16) subnet_field = Entry(16) gateway_field = Entry(16) dns_field = Entry(16) vlan_field = Entry(16) - if defaults and defaults.isStatic4(): - # static configuration defined previously - dhcp_rb = SingleRadioButton("Automatic configuration (DHCP)", None, 0) - dhcp_rb.setCallback(dhcp_change, ()) - static_rb = SingleRadioButton("Static configuration:", dhcp_rb, 1) - static_rb.setCallback(dhcp_change, ()) - if defaults.ipaddr: - ip_field.set(defaults.ipaddr) - if defaults.netmask: - subnet_field.set(defaults.netmask) - if defaults.gateway: - gateway_field.set(defaults.gateway) + static = bool(defaults and (defaults.modev6 if ipv6 else defaults.mode) == NetInterface.Static) + dhcp_rb = SingleRadioButton("Automatic configuration (DHCP)", None, not static) + dhcp_rb.setCallback(dhcp_change, ()) + static_rb = SingleRadioButton("Static configuration:", dhcp_rb, static) + static_rb.setCallback(dhcp_change, ()) + autoconf_rb = SingleRadioButton("Automatic configuration (Autoconf)", static_rb, 0) + autoconf_rb.setCallback(dhcp_change, ()) + dhcp_change() + + if defaults: + if ipv6: + if defaults.ipv6addr: + ip6addr, netmask = defaults.ipv6addr.split("/") + ip_field.set(ip6addr) + subnet_field.set(netmask) + if defaults.ipv6_gateway: + gateway_field.set(defaults.ipv6_gateway) + else: + if defaults.ipaddr: + ip_field.set(defaults.ipaddr) + if defaults.netmask: + subnet_field.set(defaults.netmask) + if defaults.gateway: + gateway_field.set(defaults.gateway) + if defaults.dns: dns_field.set(defaults.dns[0]) - else: - dhcp_rb = SingleRadioButton("Automatic configuration (DHCP)", None, 1) - dhcp_rb.setCallback(dhcp_change, ()) - static_rb = SingleRadioButton("Static configuration:", dhcp_rb, 0) - static_rb.setCallback(dhcp_change, ()) - ip_field.setFlags(FLAG_DISABLED, False) - subnet_field.setFlags(FLAG_DISABLED, False) - gateway_field.setFlags(FLAG_DISABLED, False) - dns_field.setFlags(FLAG_DISABLED, False) vlan_cb = Checkbox("Use VLAN:", defaults.isVlan() if defaults else False) vlan_cb.setCallback(use_vlan_cb_change, ()) @@ -66,8 +74,10 @@ def dhcp_change(): else: vlan_field.setFlags(FLAG_DISABLED, False) - ip_text = Textbox(15, 1, "IP Address:") - subnet_text = Textbox(15, 1, "Subnet mask:") + ip_msg = "IPv6 Address" if ipv6 else "IP Address" + mask_msg = "CIDR (4-128)" if ipv6 else "Subnet mask" + ip_text = Textbox(15, 1, "%s:" % ip_msg) + subnet_text = Textbox(15, 1, "%s:" % mask_msg) gateway_text = Textbox(15, 1, "Gateway:") dns_text = Textbox(15, 1, "Nameserver:") vlan_text = Textbox(15, 1, "VLAN (1-4094):") @@ -91,11 +101,14 @@ def dhcp_change(): gf.add(dhcp_rb, 0, 2, anchorLeft=True) gf.add(static_rb, 0, 3, anchorLeft=True) gf.add(entry_grid, 0, 4, padding=(0, 0, 0, 1)) - gf.add(vlan_cb, 0, 5, anchorLeft=True) - gf.add(vlan_grid, 0, 6, padding=(0, 0, 0, 1)) - gf.add(buttons, 0, 7, growx=1) + if ipv6: + gf.add(autoconf_rb, 0, 5, anchorLeft=True) + gf.add(vlan_cb, 0, 6, anchorLeft=True) + gf.add(vlan_grid, 0, 7, padding=(0, 0, 0, 1)) + gf.add(buttons, 0, 8, growx=1) loop = True + ip_family = socket.AF_INET6 if ipv6 else socket.AF_INET while loop: result = gf.run() @@ -103,13 +116,14 @@ def dhcp_change(): # validate input msg = '' if static_rb.selected(): - if not netutil.valid_ip_addr(ip_field.value()): - msg = 'IP Address' - elif not netutil.valid_ip_addr(subnet_field.value()): - msg = 'Subnet mask' - elif gateway_field.value() != '' and not netutil.valid_ip_addr(gateway_field.value()): + invalid_subnet = int(subnet_field.value()) > 128 or int(subnet_field.value()) < 4 if ipv6 else not netutil.valid_ipv4_addr(subnet_field.value()) + if not netutil.valid_ip_address_family(ip_field.value(), ip_family): + msg = ip_msg + elif invalid_subnet: + msg = mask_msg + elif gateway_field.value() != '' and not netutil.valid_ip_address_family(gateway_field.value(), ip_family): msg = 'Gateway' - elif dns_field.value() != '' and not netutil.valid_ip_addr(dns_field.value()): + elif dns_field.value() != '' and not netutil.valid_ip_address_family(dns_field.value(), ip_family): msg = 'Nameserver' if vlan_cb.selected(): if not netutil.valid_vlan(vlan_field.value()): @@ -126,12 +140,15 @@ def dhcp_change(): if buttons.buttonPressed(result) == 'back': return LEFT_BACKWARDS, None vlan_value = int(vlan_field.value()) if vlan_cb.selected() else None - if bool(dhcp_rb.selected()): - answers = NetInterface(NetInterface.DHCP, nic.hwaddr, vlan=vlan_value) + if dhcp_rb.selected(): + answers = iface_class(NetInterface.DHCP, nic.hwaddr, vlan=vlan_value) + elif ipv6 and autoconf_rb.selected(): + answers = iface_class(NetInterface.Autoconf, nic.hwaddr, vlan=vlan_value) else: - answers = NetInterface(NetInterface.Static, nic.hwaddr, ip_field.value(), - subnet_field.value(), gateway_field.value(), - dns_field.value(), vlan=vlan_value) + answers = iface_class(NetInterface.Static, nic.hwaddr, ip_field.value(), + subnet_field.value(), gateway_field.value(), + dns_field.value(), vlan=vlan_value) + return RIGHT_FORWARDS, answers direction, answers = get_ip_configuration(nic, txt, defaults, include_dns, NetInterface) From 578c7c0381bbe714ea792e91bdec726e8c61794c Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Thu, 15 Jun 2023 10:04:25 +0200 Subject: [PATCH 14/19] Add IP family selection in TUI IPv4 for IPv4 only IPv6 for IPv6 only Dual for both (IPv4 will be primary in XAPI) Signed-off-by: BenjiReis --- tui/network.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/tui/network.py b/tui/network.py index ceff83eb..30edd293 100644 --- a/tui/network.py +++ b/tui/network.py @@ -15,6 +15,47 @@ from snack import * def get_iface_configuration(nic, txt=None, defaults=None, include_dns=False): + def choose_primary_address_type(nic): + gf = GridFormHelp(tui.screen, 'Networking', 'Address type', 1, 8) + txt = "Choose an address type for %s (%s)" % (nic.name, nic.hwaddr) + text = TextboxReflowed(45, txt) + + b = [("Ok", "ok"), ("Back", "back")] + buttons = ButtonBar(tui.screen, b) + + # IPv4 by default + ipv4_rb = SingleRadioButton("IPv4", None, 1) + ipv6_rb = SingleRadioButton("IPv6", ipv4_rb, 0) + dual_rb = SingleRadioButton("Dual stack (IPv4 primary)", ipv6_rb, 0) + + gf.add(text, 0, 0, padding=(0, 0, 0, 1)) + gf.add(ipv4_rb, 0, 2, anchorLeft=True) + gf.add(ipv6_rb, 0, 3, anchorLeft=True) + gf.add(dual_rb, 0, 4, anchorLeft=True) + gf.add(buttons, 0, 5, growx=1) + + loop = True + direction = LEFT_BACKWARDS + address_type = None + while loop: + result = gf.run() + if buttons.buttonPressed(result) == 'back': + loop = False + elif buttons.buttonPressed(result) == 'ok': + value = None + if ipv4_rb.selected(): + value = "ipv4" + elif ipv6_rb.selected(): + value = "ipv6" + elif dual_rb.selected(): + value = "dual" + loop = False + direction = RIGHT_FORWARDS + address_type = value + + tui.screen.popWindow() + return direction, address_type + def get_ip_configuration(nic, txt, defaults, include_dns, iface_class): def use_vlan_cb_change(): vlan_field.setFlags(FLAG_DISABLED, vlan_cb.value()) @@ -151,9 +192,30 @@ def dhcp_change(): return RIGHT_FORWARDS, answers - direction, answers = get_ip_configuration(nic, txt, defaults, include_dns, NetInterface) + direction, address_type = choose_primary_address_type(nic) if direction == LEFT_BACKWARDS: + return LEFT_BACKWARDS, None + + answers = None + if address_type in ["ipv4", "dual"]: + direction, answers = get_ip_configuration(nic, txt, defaults, include_dns, NetInterface) + if direction == LEFT_BACKWARDS: return LEFT_BACKWARDS, None + + if address_type in ["ipv6", "dual"]: + direction, answers_ipv6 = get_ip_configuration(nic, txt, defaults, include_dns, NetInterfaceV6) + if direction == LEFT_BACKWARDS: + return LEFT_BACKWARDS, None + + if answers == None: + answers = answers_ipv6 + else: + answers.modev6 = answers_ipv6.modev6 + answers.ipv6addr = answers_ipv6.ipv6addr + answers.ipv6_gateway = answers_ipv6.ipv6_gateway + if answers_ipv6.dns != None: + answers.dns = answers_ipv6.dns if answers.dns == None else answers.dns + answers_ipv6.dns + return RIGHT_FORWARDS, answers def select_netif(text, conf, offer_existing=False, default=None): From 6b5f1eb8a67d0bbad9d9e076bce1de8e418149e5 Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Thu, 1 Jun 2023 14:30:18 +0200 Subject: [PATCH 15/19] `waitUntilUp` also wait for IPv6 if statically configured Need to add `ndisc6` to the install.img Signed-off-by: BenjiReis --- netinterface.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/netinterface.py b/netinterface.py index fbd856f0..32b4727e 100644 --- a/netinterface.py +++ b/netinterface.py @@ -213,14 +213,18 @@ def writeRHStyleInterface(self, iface): def waitUntilUp(self, iface): - if not self.isStatic4(): - return True - if not self.gateway: - return True - - rc = util.runCmd2(['/usr/sbin/arping', '-f', '-w', '120', '-I', - self.getInterfaceName(iface), self.gateway]) - return rc == 0 + iface_name = self.getInterfaceName(iface) + if self.isStatic4() and self.gateway and util.runCmd2( + ['/usr/sbin/arping', '-f', '-w', '120', '-I', iface_name, self.gateway] + ): + return False + + if self.isStatic6() and self.ipv6_gateway and util.runCmd2( + ['/usr/sbin/ndisc6', '-1', '-w', '120', self.ipv6_gateway, iface_name] + ): + return False + + return True @staticmethod def getModeStr(mode): From 2d680c0c73b9c5d24e895e5594b649f59247903b Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Thu, 1 Jun 2023 15:17:12 +0200 Subject: [PATCH 16/19] Hide dynamic buttons in tui when in static mode Add `isDynamic` helper to `NetInterface` Signed-off-by: BenjiReis --- netinterface.py | 4 ++++ netutil.py | 2 +- tui/installer/screens.py | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/netinterface.py b/netinterface.py index 32b4727e..9aee706c 100644 --- a/netinterface.py +++ b/netinterface.py @@ -130,6 +130,10 @@ def isStatic6(self): """ Returns true if an IPv6 static interface configuration is represented. """ return self.modev6 == self.Static + def isDynamic(self): + """ Returns true if a dynamic interface configuration is represented. """ + return self.mode == self.DHCP or self.modev6 == self.DHCP or self.modev6 == self.Autoconf + def isVlan(self): return self.vlan is not None diff --git a/netutil.py b/netutil.py index d989edd7..a1863f01 100644 --- a/netutil.py +++ b/netutil.py @@ -92,7 +92,7 @@ def writeResolverFile(configuration, filename): for iface in configuration: settings = configuration[iface] - if settings.isStatic4() and settings.dns: + if (not settings.isDynamic()) and settings.dns: if settings.dns: for server in settings.dns: outfile.write("nameserver %s\n" % server) diff --git a/tui/installer/screens.py b/tui/installer/screens.py index fef4c4b4..fdc4e224 100644 --- a/tui/installer/screens.py +++ b/tui/installer/screens.py @@ -805,7 +805,7 @@ def ns_callback((enabled, )): for entry in [ns1_entry, ns2_entry, ns3_entry]: entry.setFlags(FLAG_DISABLED, enabled) - hide_rb = answers['net-admin-configuration'].isStatic4() + hide_rb = not answers['net-admin-configuration'].isDynamic() # HOSTNAME: hn_title = Textbox(len("Hostname Configuration"), 1, "Hostname Configuration") @@ -935,7 +935,7 @@ 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'].isStatic4(): + if 'net-admin-configuration' in answers and not answers['net-admin-configuration'].isDynamic(): answers['net-admin-configuration'].dns = answers['manual-nameservers'][1] else: answers['manual-nameservers'] = (False, None) @@ -1036,7 +1036,7 @@ 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'].isStatic4() + hide_cb = not answers['net-admin-configuration'].isDynamic() 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)?") From 14b75e369c226e00b1549e707b1f9b30c485fe8b Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Wed, 7 Jun 2023 11:08:38 +0200 Subject: [PATCH 17/19] Keep IPv6 enablement/disablement upon upgrades Add `etc/sysctl.d/91-net-ipv6.conf` in the restore list Signed-off-by: BenjiReis --- upgrade.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/upgrade.py b/upgrade.py index ec8d9104..8a4b0851 100644 --- a/upgrade.py +++ b/upgrade.py @@ -445,6 +445,9 @@ def buildRestoreList(self): self.restore_list += ['var/lib/xcp/verify_certificates'] + # Keep IPv6 enablement/disablement upon upgrades + self.restore_list += ['etc/sysctl.d/91-net-ipv6.conf'] + completeUpgradeArgs = ['mounts', 'installation-to-overwrite', 'primary-disk', 'backup-partnum', 'logs-partnum', 'net-admin-interface', 'net-admin-bridge', 'net-admin-configuration'] def completeUpgrade(self, mounts, prev_install, target_disk, backup_partnum, logs_partnum, admin_iface, admin_bridge, admin_config): From 7add5d1282cbb1ea1c4a22161cd6e9fab614139d Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Fri, 16 Jun 2023 10:22:19 +0200 Subject: [PATCH 18/19] Write resolver file if dns or searchdomain have specified The file should always be written if the fileds have been filled. Signed-off-by: BenjiReis --- netutil.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/netutil.py b/netutil.py index a1863f01..4c4f4169 100644 --- a/netutil.py +++ b/netutil.py @@ -92,12 +92,11 @@ def writeResolverFile(configuration, filename): for iface in configuration: settings = configuration[iface] - if (not settings.isDynamic()) and settings.dns: - if settings.dns: - for server in settings.dns: - outfile.write("nameserver %s\n" % server) - if settings.domain: - outfile.write("search %s\n" % settings.domain) + if settings.dns: + for server in settings.dns: + outfile.write("nameserver %s\n" % server) + if settings.domain: + outfile.write("search %s\n" % settings.domain) outfile.close() From 67389d64c33dfb8f53e6c3fa651edaa7269f2354 Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Mon, 19 Jun 2023 15:21:40 +0200 Subject: [PATCH 19/19] Allow to set manual DNS in TUI for autoconf Signed-off-by: BenjiReis --- netinterface.py | 6 ++++++ tui/network.py | 27 ++++++++++++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/netinterface.py b/netinterface.py index 9aee706c..ce30ae76 100644 --- a/netinterface.py +++ b/netinterface.py @@ -396,5 +396,11 @@ def __init__(self, mode, hwaddr, ipaddr=None, netmask=None, gateway=None, dns=No dns = [ dns ] self.dns = dns self.domain = domain + elif mode == self.Autoconf: + if dns == '': + dns = None + elif isinstance(dns, str): + dns = [ dns ] + self.dns = dns self.addIPv6(mode, ipv6addr=ipv6addr, ipv6gw=gateway) diff --git a/tui/network.py b/tui/network.py index 30edd293..ad08e4cc 100644 --- a/tui/network.py +++ b/tui/network.py @@ -64,6 +64,10 @@ def dhcp_change(): for x in [ ip_field, gateway_field, subnet_field, dns_field ]: x.setFlags(FLAG_DISABLED, static_rb.selected()) + # Allow manual DNS for autoconf + if autoconf_rb.selected(): + dns_field.setFlags(FLAG_DISABLED, True) + ipv6 = iface_class == NetInterfaceV6 gf = GridFormHelp(tui.screen, 'Networking', 'ifconfig', 1, 10) @@ -123,16 +127,19 @@ def dhcp_change(): dns_text = Textbox(15, 1, "Nameserver:") vlan_text = Textbox(15, 1, "VLAN (1-4094):") - entry_grid = Grid(2, include_dns and 4 or 3) + entry_grid = Grid(2, 3) entry_grid.setField(ip_text, 0, 0) entry_grid.setField(ip_field, 1, 0) entry_grid.setField(subnet_text, 0, 1) entry_grid.setField(subnet_field, 1, 1) entry_grid.setField(gateway_text, 0, 2) entry_grid.setField(gateway_field, 1, 2) + + dns_grid = None if include_dns: - entry_grid.setField(dns_text, 0, 3) - entry_grid.setField(dns_field, 1, 3) + dns_grid = Grid(2, 1) + dns_grid.setField(dns_text, 0, 0) + dns_grid.setField(dns_field, 1, 0) vlan_grid = Grid(2, 1) vlan_grid.setField(vlan_text, 0, 0) @@ -144,9 +151,11 @@ def dhcp_change(): gf.add(entry_grid, 0, 4, padding=(0, 0, 0, 1)) if ipv6: gf.add(autoconf_rb, 0, 5, anchorLeft=True) - gf.add(vlan_cb, 0, 6, anchorLeft=True) - gf.add(vlan_grid, 0, 7, padding=(0, 0, 0, 1)) - gf.add(buttons, 0, 8, growx=1) + if include_dns: + gf.add(dns_grid, 0, 6, anchorLeft=True) + gf.add(vlan_cb, 0, 7, anchorLeft=True) + gf.add(vlan_grid, 0, 8, padding=(0, 0, 0, 1)) + gf.add(buttons, 0, 9, growx=1) loop = True ip_family = socket.AF_INET6 if ipv6 else socket.AF_INET @@ -166,6 +175,9 @@ def dhcp_change(): msg = 'Gateway' elif dns_field.value() != '' and not netutil.valid_ip_address_family(dns_field.value(), ip_family): msg = 'Nameserver' + elif autoconf_rb.selected(): + if dns_field.value() != '' and not netutil.valid_ip_address_family(dns_field.value(), ip_family): + msg = 'Nameserver' if vlan_cb.selected(): if not netutil.valid_vlan(vlan_field.value()): msg = 'VLAN' @@ -184,7 +196,8 @@ def dhcp_change(): if dhcp_rb.selected(): answers = iface_class(NetInterface.DHCP, nic.hwaddr, vlan=vlan_value) elif ipv6 and autoconf_rb.selected(): - answers = iface_class(NetInterface.Autoconf, nic.hwaddr, vlan=vlan_value) + answers = iface_class(NetInterface.Autoconf, nic.hwaddr, + dns=dns_field.value(), vlan=vlan_value) else: answers = iface_class(NetInterface.Static, nic.hwaddr, ip_field.value(), subnet_field.value(), gateway_field.value(),