models.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. # -*- coding: utf-8 -*-
  2. import datetime
  3. from django.db import models
  4. from django.core.exceptions import ValidationError
  5. from django.core.validators import MaxValueValidator
  6. from django.db.models import Q
  7. from netfields import CidrAddressField, NetManager
  8. from netaddr import IPNetwork, IPSet
  9. def validate_subnet(cidr):
  10. """Checks that a CIDR object is indeed a subnet, i.e. the host bits are
  11. all set to zero."""
  12. if not isinstance(cidr, IPNetwork):
  13. raise ValidationError("Internal error, expected IPNetwork object")
  14. if cidr.ip != cidr.network:
  15. raise ValidationError("{} is not a proper subnet, you probably mean {}".format(cidr, cidr.cidr))
  16. class IPPool(models.Model):
  17. """Pool of IP addresses (either v4 or v6)."""
  18. name = models.CharField(max_length=255, blank=False, null=False,
  19. help_text='Name of the IP pool')
  20. default_subnetsize = models.PositiveSmallIntegerField(blank=False,
  21. verbose_name='default subnet size',
  22. help_text='Default subnet size to allocate to subscribers in this pool',
  23. validators=[MaxValueValidator(64)])
  24. inet = CidrAddressField(validators=[validate_subnet],
  25. help_text="Address space to use")
  26. objects = NetManager()
  27. def clean(self):
  28. if self.inet:
  29. max_subnetsize = 64 if self.inet.version == 6 else 32
  30. if not self.inet.prefixlen <= self.default_subnetsize <= max_subnetsize:
  31. raise ValidationError('Invalid default subnet size')
  32. # Check that related subnet are in the pool (useful when
  33. # modifying an existing pool that already has subnets
  34. # allocated in it)
  35. incorrect = [str(subnet) for subnet in self.ipsubnet_set.all()
  36. if not subnet.inet in self.inet]
  37. if incorrect:
  38. err = 'Some subnets allocated in this pool are outside the pool: {}'.format(incorrect)
  39. raise ValidationError(err)
  40. def __unicode__(self):
  41. return self.name
  42. class IPSubnet(models.Model):
  43. # TODO: find some way to signal to Subscriptions objects when a subnet
  44. # gets modified (so that the subscription can update the LDAP backend
  45. # accordingly)
  46. # Actually, a better idea would be to build a custom relation and update
  47. # LDAP in the relation itself.
  48. inet = CidrAddressField(blank=True, validators=[validate_subnet],
  49. verbose_name="subnet",
  50. help_text="Leave empty for automatic allocation")
  51. objects = NetManager()
  52. ip_pool = models.ForeignKey(IPPool, verbose_name="IP pool")
  53. offer_subscription = models.ForeignKey('offers.OfferSubscription',
  54. related_name='ip_subnet',
  55. verbose_name="subscription")
  56. delegate_reverse_dns = models.BooleanField(default=False)
  57. name_server = models.ManyToManyField('reverse_dns.NameServer',
  58. blank=True,
  59. help_text="Nameserver to use for the delegation of reverse DNS")
  60. def allocate(self):
  61. """Automatically allocate a free subnet"""
  62. pool = IPSet([self.ip_pool.inet])
  63. used = IPSet((s.inet for s in self.ip_pool.ipsubnet_set.all()))
  64. free = pool.difference(used)
  65. # Generator for efficiency (we don't build the whole list)
  66. available = (p for p in free.iter_cidrs() if p.prefixlen <= self.ip_pool.default_subnetsize)
  67. # TODO: for IPv4, get rid of the network and broadcast
  68. # addresses? Not really needed nowadays, and we usually don't
  69. # have a real subnet in practice (i.e. Ethernet segment), but
  70. # many /32.
  71. try:
  72. first_free = available.next()
  73. except StopIteration:
  74. raise ValidationError('Unable to allocate an IP subnet in the specified pool: not enough space left.')
  75. self.inet = first_free.subnet(self.ip_pool.default_subnetsize, 1).next()
  76. def validate_inclusion(self):
  77. """Check that we are included in the IP pool"""
  78. if not self.inet in self.ip_pool.inet:
  79. raise ValidationError('Subnet must be included in the IP pool.')
  80. # Check that we don't conflict with existing subnets.
  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. if conflicting:
  84. raise ValidationError('Subnet must not intersect with existing subnets.\nIntersected subnets: {}.'.format(conflicting))
  85. def validate_reverse_dns(self):
  86. """Check that reverse DNS entries, if any, are included in the subnet"""
  87. incorrect = [str(rev.ip) for rev in self.reversednsentry_set.all() if not rev.ip in self.inet]
  88. if incorrect:
  89. raise ValidationError('Some reverse DNS entries are not in the subnet: {}.'.format(incorrect))
  90. def clean(self):
  91. if not self.inet:
  92. self.allocate()
  93. else:
  94. self.validate_inclusion()
  95. self.validate_reverse_dns()
  96. def __unicode__(self):
  97. return str(self.inet)
  98. class IPAllocationLog(models.Model):
  99. """Tracks IP allocation for users/subscribers. Data retention laws
  100. generally impose two distincts things:
  101. 1/ Keep the data for a given duration
  102. 2/ Delete the data after this duration is expired
  103. Besides data retention, we may also want to have an history of our IP
  104. subnets.
  105. The idea of this model is that log entries are kept forever. However,
  106. as old users and subscriptions get deleted over time, the log entries
  107. will contain less and less information. Eventually, we will just know
  108. things like "such subnet was used at such time", without any idea on
  109. who the user was.
  110. """
  111. # All we need to do is automatically fill this in upon other objects'
  112. # creation/destruction/deactivation.
  113. subnet = CidrAddressField()
  114. user = models.ForeignKey('members.Member', null=True, blank=True,
  115. on_delete=models.SET_NULL)
  116. subscription = models.ForeignKey('offers.OfferSubscription', null=True,
  117. blank=True, on_delete=models.SET_NULL)
  118. offer = models.ForeignKey('offers.Offer', null=True, blank=True,
  119. on_delete=models.SET_NULL)
  120. start_date = models.DateField(default=datetime.date.today)
  121. stop_date = models.DateField(null=True, blank=True)
  122. objects = NetManager()
  123. def __unicode__(self):
  124. return str(self.subnet)