1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/interfaces/forms.py
catborise 55eb63a09f fix(interfaces): an ethernet interface needs no bridge member, and unreadable host devices are reported
The form required a bridge member for an ethernet interface, which does
not use one. When the host's network devices could not be read, the error
was swallowed and every creation failed with "the device is not on this
host"; the page now shows the real error and does not try.
2026-10-08 08:50:21 +03:00

125 lines
5.2 KiB
Python

import ipaddress
import re
from django import forms
from django.utils.translation import gettext_lazy as _
class AddInterface(forms.Form):
name = forms.CharField(max_length=15, required=True)
itype = forms.ChoiceField(
required=True, choices=(("bridge", "bridge"), ("ethernet", "ethernet"))
)
start_mode = forms.ChoiceField(
required=True,
choices=(("none", "none"), ("onboot", "onboot"), ("hotplug", "hotplug")),
)
# a bridge's member; an ethernet interface configures the device in name
netdev = forms.CharField(max_length=15, required=False)
ipv4_type = forms.ChoiceField(
required=True,
choices=(("dhcp", "dhcp"), ("static", "static"), ("none", "none")),
)
# room for a dotted netmask: 192.0.2.10/255.255.255.0
ipv4_addr = forms.CharField(max_length=31, required=False)
ipv4_gw = forms.CharField(max_length=15, required=False)
ipv6_type = forms.ChoiceField(
required=True,
choices=(("dhcp", "dhcp"), ("static", "static"), ("none", "none")),
)
ipv6_addr = forms.CharField(max_length=100, required=False)
ipv6_gw = forms.CharField(max_length=100, required=False)
stp = forms.ChoiceField(required=False, choices=(("on", "on"), ("off", "off")))
delay = forms.IntegerField(required=False, min_value=0)
def __init__(self, *args, netdevs=(), **kwargs):
# The host's network devices: a bridge member, or the device an
# ethernet interface configures, must be one of them.
super().__init__(*args, **kwargs)
self.netdevs = set(netdevs)
def clean_ipv4_addr(self):
ipv4_addr = self.cleaned_data["ipv4_addr"]
if not ipv4_addr:
return ipv4_addr
try:
# Always address/prefix, as the interface XML needs both
return str(ipaddress.IPv4Interface(ipv4_addr))
except ValueError:
raise forms.ValidationError(_("The IPv4 address is not valid (e.g. 192.0.2.10/24)"))
def clean_ipv4_gw(self):
ipv4_gw = self.cleaned_data["ipv4_gw"]
if not ipv4_gw:
return ipv4_gw
try:
return str(ipaddress.IPv4Address(ipv4_gw))
except ValueError:
raise forms.ValidationError(_("The IPv4 gateway is not valid"))
# libvirt's interface schema has no IPv6 scope id (fe80::1%eth0)
def clean_ipv6_addr(self):
ipv6_addr = self.cleaned_data["ipv6_addr"]
if not ipv6_addr:
return ipv6_addr
try:
address = ipaddress.IPv6Interface(ipv6_addr)
except ValueError:
address = None
if address is None or address.scope_id is not None:
raise forms.ValidationError(_("The IPv6 address is not valid (e.g. 2001:db8::10/64)"))
# Always address/prefix, as the interface XML needs both
return str(address)
def clean_ipv6_gw(self):
ipv6_gw = self.cleaned_data["ipv6_gw"]
if not ipv6_gw:
return ipv6_gw
try:
gateway = ipaddress.IPv6Address(ipv6_gw)
except ValueError:
gateway = None
if gateway is None or gateway.scope_id is not None:
raise forms.ValidationError(_("The IPv6 gateway is not valid"))
return str(gateway)
def clean_name(self):
# A Linux interface name: up to 15 characters, no spaces or slashes
name = self.cleaned_data["name"]
if not re.fullmatch(r"[A-Za-z0-9_.-]{1,15}", name) or name in (".", ".."):
raise forms.ValidationError(
_("The interface name may have up to 15 letters, digits and . _ -")
)
return name
def clean_netdev(self):
netdev = self.cleaned_data["netdev"]
if not netdev:
return netdev
if not re.fullmatch(r"[A-Za-z0-9_.-]{1,15}", netdev) or netdev in (".", ".."):
raise forms.ValidationError(
_("The device name may have up to 15 letters, digits and . _ -")
)
return netdev
def clean(self):
cleaned = super().clean()
for family in ("ipv4", "ipv6"):
addr = f"{family}_addr"
if cleaned.get(f"{family}_type") == "static" and not cleaned.get(addr) and addr not in self.errors:
self.add_error(addr, _("A static configuration needs an address"))
name, netdev = cleaned.get("name"), cleaned.get("netdev")
if cleaned.get("itype") == "bridge":
if not cleaned.get("stp") and "stp" not in self.errors:
self.add_error("stp", _("A bridge needs STP on or off"))
if cleaned.get("delay") is None and "delay" not in self.errors:
self.add_error("delay", _("A bridge needs a forward delay"))
if not netdev and "netdev" not in self.errors:
self.add_error("netdev", _("A bridge needs a device"))
elif netdev and netdev not in self.netdevs:
self.add_error("netdev", _("The device is not on this host"))
elif netdev and netdev == name:
self.add_error("netdev", _("A bridge cannot contain itself"))
elif cleaned.get("itype") == "ethernet" and name and name not in self.netdevs:
self.add_error("name", _("An ethernet interface configures a device of this host"))
return cleaned