Commit 6299201d by Őry Máté

firewall: add sensible default and help_text to Vlan.ipv6_template

closes #360
parent 8f0735c7
......@@ -19,6 +19,7 @@
from string import ascii_letters
from itertools import islice, ifilter, chain
from math import ceil
import logging
import random
......@@ -35,7 +36,7 @@ from django.core.validators import MinValueValidator, MaxValueValidator
import django.conf
from django.db.models.signals import post_save, post_delete
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 firewall.tasks.local_tasks import reloadtask
......@@ -363,9 +364,20 @@ class Vlan(AclBase, models.Model):
'address is: "%(d)d.%(c)d.%(b)d.%(a)".'),
ipv6_template = models.TextField(
verbose_name=_('ipv6 template'),
help_text=_('Template for translating IPv4 addresses to IPv6. '
'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 '
' to 2001:0DB8:1:1::/64 would be '
'"2001:0DB8:51:1:1:%(d)d::" and '
  • "2001:0DB8:1:1:%(d)d::"

  • >>> Vlan._magic_ipv6_template(IPNetwork(""),
    ...                           IPNetwork("2001:0DB8:1:1::/64"))
    >>> Vlan._magic_ipv6_template(IPNetwork(""),
    ...                           IPNetwork("2001:0DB8:1:1::/64"), 0)
Please register or sign in to reply
validators=[val_ipv6_template], verbose_name=_('ipv6 template'))
dhcp_pool = models.TextField(blank=True, verbose_name=_('DHCP pool'),
'The address range of the DHCP pool: '
......@@ -380,6 +392,52 @@ class Vlan(AclBase, models.Model):
modified_at = models.DateTimeField(auto_now=True,
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))
Please register or sign in to reply
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 =
s = ipstr.split(":")[0:-remove]
if 2 * (host4_bytes + 1) < host6_bytes: # use verbose format
for i in letters:
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)
remain -= 1
if host6_bytes > host4_bytes:
return ":".join(s)
def __unicode__(self):
return "%s - %s" % ("managed" if self.managed else "unmanaged",
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment