Skip to content

Commit

Permalink
Configure IPv6 in TUI
Browse files Browse the repository at this point in the history
Add a screen to choose to configure IPv4, IPv6 or both
Display the interface screen for IPv4 and/or IPv6

Signed-off-by: BenjiReis <benjamin.reis@vates.fr>
  • Loading branch information
benjamreis committed Jun 5, 2023
1 parent 96ab336 commit 3c6200f
Showing 1 changed file with 227 additions and 126 deletions.
353 changes: 227 additions & 126 deletions tui/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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

0 comments on commit 3c6200f

Please sign in to comment.