firewall: add sensible default and help_text to Vlan.ipv6_template
closes #360
Showing
closes #360
... | @@ -19,6 +19,7 @@ | ... | @@ -19,6 +19,7 @@ |
from string import ascii_letters | from string import ascii_letters | ||
from itertools import islice, ifilter, chain | from itertools import islice, ifilter, chain | ||
from math import ceil | |||
import logging | import logging | ||
import random | import random | ||
... | @@ -35,7 +36,7 @@ from django.core.validators import MinValueValidator, MaxValueValidator | ... | @@ -35,7 +36,7 @@ from django.core.validators import MinValueValidator, MaxValueValidator |
import django.conf | import django.conf | ||
from django.db.models.signals import post_save, post_delete | from django.db.models.signals import post_save, post_delete | ||
from celery.exceptions import TimeoutError | from celery.exceptions import TimeoutError | ||
from netaddr import IPSet, EUI, IPNetwork, IPAddress | from netaddr import IPSet, EUI, IPNetwork, IPAddress, ipv6_full | ||
from common.models import method_cache, WorkerNotFound, HumanSortField | from common.models import method_cache, WorkerNotFound, HumanSortField | ||
from firewall.tasks.local_tasks import reloadtask | from firewall.tasks.local_tasks import reloadtask | ||
... | @@ -363,9 +364,20 @@ class Vlan(AclBase, models.Model): | ... | @@ -363,9 +364,20 @@ class Vlan(AclBase, models.Model): |
'address is: "%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa".'), | 'address is: "%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa".'), | ||
default="%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa") | default="%(d)d.%(c)d.%(b)d.%(a)d.in-addr.arpa") | ||
ipv6_template = models.TextField( | ipv6_template = models.TextField( | ||
validators=[val_ipv6_template], | blank=True, | ||
verbose_name=_('ipv6 template'), | help_text=_('Template for translating IPv4 addresses to IPv6. ' | ||
default="2001:738:2001:4031:%(b)d:%(c)d:%(d)d:0") | 'Automatically generated hosts in dual-stack networks ' | ||
'will get this address. The template ' | |||
'can contain four tokens: "%(a)d", "%(b)d", ' | |||
'"%(c)d", and "%(d)d", representing the four bytes ' | |||
'of the IPv4 address, respectively, in decimal notation. ' | |||
'Moreover you can use any standard printf format ' | |||
'specification like %(a)02x to get the first byte as two ' | |||
'hexadecimal digits. Usual choices for mapping ' | |||
'198.51.100.0/24 to 2001:0DB8:1:1::/64 would be ' | |||
'"2001:0DB8:51:1:1:%(d)d::" and ' | |||
Please
register
or
sign in
to reply
|
|||
'"2001:0DB8:51:1:1:%(d)x00::".'), | |||
validators=[val_ipv6_template], verbose_name=_('ipv6 template')) | |||
dhcp_pool = models.TextField(blank=True, verbose_name=_('DHCP pool'), | dhcp_pool = models.TextField(blank=True, verbose_name=_('DHCP pool'), | ||
help_text=_( | help_text=_( | ||
'The address range of the DHCP pool: ' | 'The address range of the DHCP pool: ' | ||
... | @@ -380,6 +392,52 @@ class Vlan(AclBase, models.Model): | ... | @@ -380,6 +392,52 @@ class Vlan(AclBase, models.Model): |
modified_at = models.DateTimeField(auto_now=True, | modified_at = models.DateTimeField(auto_now=True, | ||
verbose_name=_('modified at')) | verbose_name=_('modified at')) | ||
def clean(self): | |||
super(Vlan, self).clean() | |||
if self.ipv6_template: | |||
if not self.network6: | |||
raise ValidationError( | |||
_("You cannot specify an IPv6 template if there is no " | |||
"IPv6 network set.")) | |||
for i in (self.network4[1], self.network4[-1]): | |||
i6 = self.convert_ipv4_to_ipv6(i) | |||
if i6 not in self.network6: | |||
raise ValidationError( | |||
_("%(ip6)s (translated from %(ip4)s) is outside of " | |||
"the IPv6 network.") % {"ip4": i, "ip6": i6}) | |||
if not self.ipv6_template and self.network6: | |||
# come up with some sensible default | |||
self.ipv6_template = self._magic_ipv6_template() | |||
host4_bytes = int(ceil((32 - self.network4.prefixlen) / 8)) | |||
|
|||
host6_bytes = int(ceil((128 - self.network6.prefixlen) / 8)) | |||
if host4_bytes > host6_bytes: | |||
raise ValidationError( | |||
_("IPv6 network is too small to map IPv4 addresses to it.")) | |||
def _magic_ipv6_template(self): | |||
host4_bytes = int(ceil((32 - self.network4.prefixlen) / 8)) | |||
host6_bytes = int(ceil((128 - self.network6.prefixlen) / 8)) | |||
letters = ascii_letters[4-host4_bytes:4] | |||
remove = host6_bytes // 2 | |||
ipstr = self.network6.network.format(ipv6_full) | |||
s = ipstr.split(":")[0:-remove] | |||
if 2 * (host4_bytes + 1) < host6_bytes: # use verbose format | |||
for i in letters: | |||
s.append("%({})d".format(i)) | |||
else: # use short format | |||
remain = host6_bytes | |||
for i in letters: | |||
if remain % 2 == 1: # can use last half word | |||
if s[-1].endswith("00"): | |||
s[-1] = s[-1][:-2] | |||
s[-1] += "%({})02x".format(i) | |||
else: | |||
s.append("%({})02x00".format(i)) | |||
remain -= 1 | |||
if host6_bytes > host4_bytes: | |||
s.append(":") | |||
return ":".join(s) | |||
def __unicode__(self): | def __unicode__(self): | ||
return "%s - %s" % ("managed" if self.managed else "unmanaged", | return "%s - %s" % ("managed" if self.managed else "unmanaged", | ||
self.name) | self.name) | ||
... | ... |