mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-09 03:36:48 +00:00
fix(interfaces): a host interface can be created with DHCP or no IPv4, and every value is validated
The IPv4 address and gateway were required even for DHCP or no IPv4, so such an interface could not be created; malformed addresses passed and a missing prefix crashed the XML builder. They are validated like the IPv6 fields now (netmask notation accepted, IPv6 scope ids rejected), and a static address without a gateway gets no empty route. Interface names follow the Linux rule (up to 15 letters, digits, . _ -). A bridge member, or the device an ethernet interface configures, must be a device of the host (the page no longer falls back to made-up names), a bridge cannot contain itself, and needs STP and a non-negative delay.
This commit is contained in:
parent
3c0d5efb18
commit
443b31d86b
5 changed files with 229 additions and 58 deletions
|
|
@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
|
||||
|
||||
class AddInterface(forms.Form):
|
||||
name = forms.CharField(max_length=10, required=True)
|
||||
name = forms.CharField(max_length=15, required=True)
|
||||
itype = forms.ChoiceField(
|
||||
required=True, choices=(("bridge", "bridge"), ("ethernet", "ethernet"))
|
||||
)
|
||||
|
|
@ -19,7 +19,8 @@ class AddInterface(forms.Form):
|
|||
required=True,
|
||||
choices=(("dhcp", "dhcp"), ("static", "static"), ("none", "none")),
|
||||
)
|
||||
ipv4_addr = forms.CharField(max_length=18, required=False)
|
||||
# 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,
|
||||
|
|
@ -28,81 +29,92 @@ class AddInterface(forms.Form):
|
|||
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)
|
||||
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"]
|
||||
have_symbol = re.match("^[0-9./]+$", ipv4_addr)
|
||||
if not have_symbol:
|
||||
raise forms.ValidationError(
|
||||
_("The IPv4 address must not contain any special characters")
|
||||
)
|
||||
elif len(ipv4_addr) > 20:
|
||||
raise forms.ValidationError(
|
||||
_("The IPv4 address must not exceed 20 characters")
|
||||
)
|
||||
return 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"]
|
||||
have_symbol = re.match("^[0-9.]+$", ipv4_gw)
|
||||
if not have_symbol:
|
||||
raise forms.ValidationError(
|
||||
_("The IPv4 gateway must not contain any special characters")
|
||||
)
|
||||
elif len(ipv4_gw) > 20:
|
||||
raise forms.ValidationError(
|
||||
_("The IPv4 gateway must not exceed 20 characters")
|
||||
)
|
||||
return 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:
|
||||
# Always address/prefix, as the interface XML needs both
|
||||
return str(ipaddress.IPv6Interface(ipv6_addr))
|
||||
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:
|
||||
return str(ipaddress.IPv6Address(ipv6_gw))
|
||||
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"))
|
||||
|
||||
def clean(self):
|
||||
cleaned = super().clean()
|
||||
if cleaned.get("ipv6_type") == "static" and not cleaned.get("ipv6_addr") and "ipv6_addr" not in self.errors:
|
||||
self.add_error("ipv6_addr", _("A static IPv6 configuration needs an address"))
|
||||
return cleaned
|
||||
return str(gateway)
|
||||
|
||||
def clean_name(self):
|
||||
# A Linux interface name: up to 15 characters, no spaces or slashes
|
||||
name = self.cleaned_data["name"]
|
||||
have_symbol = re.match("^[a-z0-9.]+$", name)
|
||||
if not have_symbol:
|
||||
if not re.fullmatch(r"[A-Za-z0-9_.-]{1,15}", name) or name in (".", ".."):
|
||||
raise forms.ValidationError(
|
||||
_("The interface must not contain any special characters")
|
||||
)
|
||||
elif len(name) > 10:
|
||||
raise forms.ValidationError(
|
||||
_("The interface must not exceed 10 characters")
|
||||
_("The interface name may have up to 15 letters, digits and . _ -")
|
||||
)
|
||||
return name
|
||||
|
||||
def clean_netdev(self):
|
||||
netdev = self.cleaned_data["netdev"]
|
||||
have_symbol = re.match("^[a-z0-9.:]+$", netdev)
|
||||
if not have_symbol:
|
||||
if not re.fullmatch(r"[A-Za-z0-9_.-]{1,15}", netdev) or netdev in (".", ".."):
|
||||
raise forms.ValidationError(
|
||||
_("The interface must not contain any special characters")
|
||||
)
|
||||
elif len(netdev) > 10:
|
||||
raise forms.ValidationError(
|
||||
_("The interface must not exceed 10 characters")
|
||||
_("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 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue