123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- # -*- coding: utf-8 -*-
- from __future__ import unicode_literals
- from django.db import models
- from django.core.exceptions import ValidationError
- from django.core.validators import MaxValueValidator
- from django.db.models import Q
- from netfields import CidrAddressField, NetManager
- from netaddr import IPNetwork, IPSet
- def validate_subnet(cidr):
- """Checks that a CIDR object is indeed a subnet, i.e. the host bits are
- all set to zero."""
- if not isinstance(cidr, IPNetwork):
- raise ValidationError("Erreur, objet IPNetwork attendu.")
- if cidr.ip != cidr.network:
- raise ValidationError("{} n'est pas un sous-réseau valide, voulez-vous dire {} ?".format(cidr, cidr.cidr))
- class IPPool(models.Model):
- """Pool of IP addresses (either v4 or v6)."""
- name = models.CharField(max_length=255, blank=False, null=False,
- verbose_name='nom',
- help_text="Nom du pool d'IP")
- default_subnetsize = models.PositiveSmallIntegerField(blank=False,
- verbose_name='taille de sous-réseau par défaut',
- help_text='Taille par défaut du sous-réseau à allouer aux abonnés dans ce pool',
- validators=[MaxValueValidator(64)])
- inet = CidrAddressField(validators=[validate_subnet],
- verbose_name='réseau',
- help_text="Bloc d'adresses IP du pool")
- objects = NetManager()
- def clean(self):
- if self.inet:
- max_subnetsize = 64 if self.inet.version == 6 else 32
- if not self.inet.prefixlen <= self.default_subnetsize <= max_subnetsize:
- raise ValidationError('Taille de sous-réseau invalide')
- # Check that related subnet are in the pool (useful when
- # modifying an existing pool that already has subnets
- # allocated in it)
- incorrect = [str(subnet) for subnet in self.ipsubnet_set.all()
- if not subnet.inet in self.inet]
- if incorrect:
- err = "Des sous-réseaux se retrouveraient en-dehors du bloc d'IP: {}".format(incorrect)
- raise ValidationError(err)
- def __unicode__(self):
- return self.name
- class Meta:
- verbose_name = "pool d'IP"
- verbose_name_plural = "pools d'IP"
- class IPSubnet(models.Model):
- # TODO: find some way to signal to Subscriptions objects when a subnet
- # gets modified (so that the subscription can update the LDAP backend
- # accordingly)
- # Actually, a better idea would be to build a custom relation and update
- # LDAP in the relation itself.
- inet = CidrAddressField(blank=True, validators=[validate_subnet],
- verbose_name="sous-réseau",
- help_text="Laisser vide pour allouer automatiquement")
- objects = NetManager()
- ip_pool = models.ForeignKey(IPPool, verbose_name="pool d'IP")
- configuration = models.ForeignKey('configuration.Configuration',
- related_name='ip_subnet',
- verbose_name='configuration')
- delegate_reverse_dns = models.BooleanField(default=False,
- verbose_name='déléguer le reverse DNS',
- help_text='Déléguer la résolution DNS inverse de ce sous-réseau à un ou plusieurs serveurs de noms')
- name_server = models.ManyToManyField('reverse_dns.NameServer',
- blank=True,
- verbose_name='serveur de noms',
- help_text="Serveur de noms à qui déléguer la résolution DNS inverse")
- def allocate(self):
- """Automatically allocate a free subnet"""
- pool = IPSet([self.ip_pool.inet])
- used = IPSet((s.inet for s in self.ip_pool.ipsubnet_set.all()))
- free = pool.difference(used)
- # Generator for efficiency (we don't build the whole list)
- available = (p for p in free.iter_cidrs() if p.prefixlen <= self.ip_pool.default_subnetsize)
- # TODO: for IPv4, get rid of the network and broadcast
- # addresses? Not really needed nowadays, and we usually don't
- # have a real subnet in practice (i.e. Ethernet segment), but
- # many /32.
- try:
- first_free = available.next()
- except StopIteration:
- raise ValidationError("Impossible d'allouer un sous-réseau : bloc d'IP rempli.")
- self.inet = first_free.subnet(self.ip_pool.default_subnetsize, 1).next()
- def validate_inclusion(self):
- """Check that we are included in the IP pool"""
- if not self.inet in self.ip_pool.inet:
- raise ValidationError("Le sous-réseau doit être inclus dans le bloc d'IP.")
- # Check that we don't conflict with existing subnets.
- conflicting = self.ip_pool.ipsubnet_set.filter(Q(inet__net_contained_or_equal=self.inet) |
- Q(inet__net_contains_or_equals=self.inet)).exclude(id=self.id)
- if conflicting:
- raise ValidationError("Le sous-réseau est en conflit avec des sous-réseaux existants: {}.".format(conflicting))
- def validate_reverse_dns(self):
- """Check that reverse DNS entries, if any, are included in the subnet"""
- incorrect = [str(rev.ip) for rev in self.reversednsentry_set.all() if not rev.ip in self.inet]
- if incorrect:
- raise ValidationError("Des entrées DNS inverse ne sont pas dans le sous-réseau: {}.".format(incorrect))
- def clean(self):
- if not self.inet:
- self.allocate()
- else:
- self.validate_inclusion()
- self.validate_reverse_dns()
- def __unicode__(self):
- return str(self.inet)
- class Meta:
- verbose_name = "sous-réseau IP"
- verbose_name_plural = "sous-réseaux IP"
|