models.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from django.db import models
  4. from django.core.exceptions import ValidationError
  5. from django.core.validators import MaxValueValidator
  6. from netfields import CidrAddressField, NetManager
  7. from netaddr import IPSet
  8. class IPPool(models.Model):
  9. """Pool of IP addresses (either v4 or v6)."""
  10. name = models.CharField(max_length=255, blank=False, null=False,
  11. verbose_name='nom',
  12. help_text="Nom du pool d'IP")
  13. default_subnetsize = models.PositiveSmallIntegerField(blank=False,
  14. verbose_name='taille de sous-réseau par défaut',
  15. help_text='Taille par défaut du sous-réseau à allouer aux abonnés dans ce pool',
  16. validators=[MaxValueValidator(64)])
  17. inet = CidrAddressField(verbose_name='réseau',
  18. help_text="Bloc d'adresses IP du pool")
  19. objects = NetManager()
  20. def clean(self):
  21. if self.inet:
  22. max_subnetsize = 64 if self.inet.version == 6 else 32
  23. if not self.inet.prefixlen <= self.default_subnetsize <= max_subnetsize:
  24. raise ValidationError('Taille de sous-réseau invalide')
  25. # Check that related subnet are in the pool (useful when
  26. # modifying an existing pool that already has subnets
  27. # allocated in it)
  28. incorrect = [str(subnet) for subnet in self.ipsubnet_set.all()
  29. if not subnet.inet in self.inet]
  30. if incorrect:
  31. err = "Des sous-réseaux se retrouveraient en-dehors du bloc d'IP: {}".format(incorrect)
  32. raise ValidationError(err)
  33. def __unicode__(self):
  34. return self.name
  35. class Meta:
  36. verbose_name = "pool d'IP"
  37. verbose_name_plural = "pools d'IP"
  38. class IPSubnet(models.Model):
  39. inet = CidrAddressField(blank=True,
  40. unique=True, verbose_name="sous-réseau",
  41. help_text="Laisser vide pour allouer automatiquement")
  42. objects = NetManager()
  43. ip_pool = models.ForeignKey(IPPool, verbose_name="pool d'IP")
  44. configuration = models.ForeignKey('configuration.Configuration',
  45. related_name='ip_subnet',
  46. verbose_name='configuration')
  47. delegate_reverse_dns = models.BooleanField(default=False,
  48. verbose_name='déléguer le reverse DNS',
  49. help_text='Déléguer la résolution DNS inverse de ce sous-réseau à un ou plusieurs serveurs de noms')
  50. name_server = models.ManyToManyField('reverse_dns.NameServer',
  51. blank=True,
  52. verbose_name='serveur de noms',
  53. help_text="Serveur de noms à qui déléguer la résolution DNS inverse")
  54. def allocate(self):
  55. """Automatically allocate a free subnet"""
  56. pool = IPSet([self.ip_pool.inet])
  57. used = IPSet((s.inet for s in self.ip_pool.ipsubnet_set.all()))
  58. free = pool.difference(used)
  59. # Generator for efficiency (we don't build the whole list)
  60. available = (p for p in free.iter_cidrs() if p.prefixlen <= self.ip_pool.default_subnetsize)
  61. # TODO: for IPv4, get rid of the network and broadcast
  62. # addresses? Not really needed nowadays, and we usually don't
  63. # have a real subnet in practice (i.e. Ethernet segment), but
  64. # many /32.
  65. try:
  66. first_free = available.next()
  67. except StopIteration:
  68. raise ValidationError("Impossible d'allouer un sous-réseau : bloc d'IP rempli.")
  69. # first_free is a subnet, but it might be too large for our needs.
  70. # This selects the first sub-subnet of the right size.
  71. self.inet = first_free.subnet(self.ip_pool.default_subnetsize, 1).next()
  72. def validate_inclusion(self):
  73. """Check that we are included in the IP pool"""
  74. if not self.inet in self.ip_pool.inet:
  75. raise ValidationError("Le sous-réseau doit être inclus dans le bloc d'IP.")
  76. # Check that we don't conflict with existing subnets.
  77. # The optimal request would be the following commented request, but
  78. # django-netfields 0.4.x seems buggy with Q-expressions. For now use
  79. # two requests, but the optimal solution will have to be retried once
  80. # we use django-netfields>=0.7
  81. #conflicting = self.ip_pool.ipsubnet_set.filter(Q(inet__net_contained_or_equal=self.inet) |
  82. # Q(inet__net_contains_or_equals=self.inet)).exclude(id=self.id)
  83. conflicting_contained = self.ip_pool.ipsubnet_set.filter(inet__net_contained_or_equal=self.inet).exclude(id=self.id)
  84. conflicting_containing = self.ip_pool.ipsubnet_set.filter(inet__net_contains_or_equals=self.inet).exclude(id=self.id)
  85. if conflicting_contained or conflicting_containing:
  86. conflicting = conflicting_contained if conflicting_contained else conflicting_containing
  87. raise ValidationError("Le sous-réseau est en conflit avec des sous-réseaux existants: {}.".format(conflicting))
  88. def validate_reverse_dns(self):
  89. """Check that reverse DNS entries, if any, are included in the subnet"""
  90. incorrect = [str(rev.ip) for rev in self.reversednsentry_set.all() if not rev.ip in self.inet]
  91. if incorrect:
  92. raise ValidationError("Des entrées DNS inverse ne sont pas dans le sous-réseau: {}.".format(incorrect))
  93. def clean(self):
  94. if not self.inet:
  95. self.allocate()
  96. else:
  97. self.validate_inclusion()
  98. self.validate_reverse_dns()
  99. def __unicode__(self):
  100. return str(self.inet)
  101. class Meta:
  102. verbose_name = "sous-réseau IP"
  103. verbose_name_plural = "sous-réseaux IP"