From 3c6200f70d9fc9cb28612f53d3b9399c2d473bc4 Mon Sep 17 00:00:00 2001 From: BenjiReis Date: Tue, 30 May 2023 11:38:10 +0200 Subject: [PATCH] Configure IPv6 in TUI Add a screen to choose to configure IPv4, IPv6 or both Display the interface screen for IPv4 and/or IPv6 Signed-off-by: BenjiReis --- tui/network.py | 353 +++++++++++++++++++++++++++++++------------------ 1 file changed, 227 insertions(+), 126 deletions(-) diff --git a/tui/network.py b/tui/network.py index 0ab6751b..e36d4133 100644 --- a/tui/network.py +++ b/tui/network.py @@ -9,128 +9,215 @@ from netinterface import * import version import os +import time +import socket 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, not dhcp_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) + 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()) + + def dhcp_change(): + for x in [ ip_field, gateway_field, subnet_field, dns_field ]: + x.setFlags(FLAG_DISABLED, static_rb.selected()) + + 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) + + 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, 0) + static_rb = SingleRadioButton("Static configuration:", dhcp_rb, static) 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) + if ipv6: + 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]) + + 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_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):") + + 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)) + if ipv6: + gf.add(autoconf_rb, 0, 5, anchorLeft=True) + # One more line for IPv6 autoconf + gf.add(vlan_cb, 0, 5 + ipv6, anchorLeft=True) + gf.add(vlan_grid, 0, 6 + ipv6, padding=(0, 0, 0, 1)) + gf.add(buttons, 0, 7 + ipv6, growx=1) + + loop = True + ip_family = socket.AF_INET6 if ipv6 else socket.AF_INET + while loop: + result = gf.run() + + if buttons.buttonPressed(result) in ['ok', None]: + # validate input + msg = '' + if static_rb.selected(): + 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_address_family(dns_field.value(), ip_family): + 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 + + tui.screen.popWindow() + + 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 = iface_class(NetInterface.DHCP, nic.hwaddr, vlan=vlan_value) + elif ipv6 and bool(autoconf_rb.selected()): + answers = iface_class(NetInterface.Autoconf, nic.hwaddr, vlan=vlan_value) else: - loop = False + answers = iface_class(NetInterface.Static, nic.hwaddr, ip_field.value(), + subnet_field.value(), gateway_field.value(), + dns_field.value(), vlan=vlan_value) - tui.screen.popWindow() + return RIGHT_FORWARDS, answers - if buttons.buttonPressed(result) == 'back': return LEFT_BACKWARDS, None + 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 - 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 def select_netif(text, conf, offer_existing=False, default=None): @@ -286,23 +373,37 @@ 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 + + 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 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