class CidrBlock(models.Model): cidr_block = CidrAddressField() netmask = models.GenericIPAddressField(protocol='IPv4', blank=True, null=True, default=None)
class NetGroupRegexPermission(models.Model): group = models.CharField(max_length=80) range = CidrAddressField() regex = models.CharField(max_length=250, validators=[validate_regex]) objects = NetManager() class Meta: db_table = 'perm_net_group_regex' unique_together = ( 'group', 'range', 'regex', ) def __str__(self): return f"group {self.group}, range {self.range}, regex {self.regex}" @staticmethod def find_perm(groups, hostname, ips): if not isinstance(hostname, str): raise ValueError(f'hostname is invalid type ({type(hostname)})') if isinstance(groups, str): groups = [groups] if not isinstance(groups, (list, tuple)): raise ValueError(f'groups on invalid type ({type(groups)})') if isinstance(ips, str): ips = [ips] if not isinstance(ips, (list, tuple)): raise ValueError(f'ips on invalid type ({type(ips)})') qs = NetGroupRegexPermission.objects.filter(group__in=groups).extra( where=["%s ~ regex"], params=[str(hostname)]).filter( reduce(lambda x, y: x | y, [Q(range__net_contains=ip) for ip in ips])) return qs
class IP4RTestModel(Model): field = CidrAddressField() objects = NetManager() class Meta: # Table name can't be type name? db_table = 'ip4rtest'
class AggregateTestChildModel(Model): parent = ForeignKey( 'AggregateTestModel', related_name='children', on_delete=CASCADE, ) network = CidrAddressField() inet = InetAddressField()
class NetworkRange(models.Model): range = CidrAddressField(unique=True) def __str__(self): return "%s" % self.range class Meta: db_table = "network_ranges"
class Token(ExportModelOperationsMixin('Token'), rest_framework.authtoken.models.Token): @staticmethod def _allowed_subnets_default(): return [ ipaddress.IPv4Network('0.0.0.0/0'), ipaddress.IPv6Network('::/0') ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) key = models.CharField("Key", max_length=128, db_index=True, unique=True) user = models.ForeignKey(User, related_name='auth_tokens', on_delete=models.CASCADE, verbose_name="User") name = models.CharField('Name', blank=True, max_length=64) last_used = models.DateTimeField(null=True, blank=True) perm_manage_tokens = models.BooleanField(default=False) allowed_subnets = ArrayField(CidrAddressField(), default=_allowed_subnets_default.__func__) max_age = models.DurationField( null=True, default=None, validators=[MinValueValidator(timedelta(0))]) max_unused_period = models.DurationField( null=True, default=None, validators=[MinValueValidator(timedelta(0))]) plain = None objects = NetManager() @property def is_valid(self): now = timezone.now() # Check max age try: if self.created + self.max_age < now: return False except TypeError: pass # Check regular usage requirement try: if (self.last_used or self.created) + self.max_unused_period < now: return False except TypeError: pass return True def generate_key(self): self.plain = secrets.token_urlsafe(21) self.key = Token.make_hash(self.plain) return self.key @staticmethod def make_hash(plain): return make_password(plain, salt='static', hasher='pbkdf2_sha256_iter1')
class IPRule(Contribution): #ip = models.GenericIPAddressField() ip = CidrAddressField() georesult = models.CharField(max_length=256) canonical_georesult = models.CharField(max_length=256, blank=True, null=True) #granularity = models.CharField(max_length=1, choices=GRANULARITIES ) default city for now lat = models.FloatField(blank=True, null=True) lon = models.FloatField(blank=True, null=True) objects = NetManager() #objects = IPRuleManager() def save(self, *args, **kwargs): if self.georesult: loc = openipmap.geoutils.loc_resolve(self.georesult) if loc and loc.raw['lat'] and loc.raw['lng']: self.lat = loc.raw['lat'] self.lon = loc.raw['lng'] cityname = '' try: cityname = loc.raw['name'] except: pass regionname = '' try: regionname = loc.raw['adminName1'] except: pass countrycode = '' try: countrycode = loc.raw['countryCode'] except: pass self.canonical_georesult = "%s,%s,%s" % (cityname, regionname, countrycode) super(IPRule, self).save(*args, **kwargs) @classmethod def get_crowdsourced(cls, ip, max_results=10): ''' returns 'max_results' number of results for this particular IP from the IPRules tables ''' results = [] ipr = IPRule.objects.filter(ip__net_contains_or_equals=ip) for rule in ipr: results.append({ 'kind': 'ip', 'granularity': 'city', 'lat': rule.lat, 'lon': rule.lon, 'georesult': rule.georesult, 'canonical_georesult': rule.canonical_georesult, 'confidence': rule.confidence, }) return results
class Lab(models.Model): name = models.CharField(max_length=30) building = models.ForeignKey(Building, on_delete=models.CASCADE, related_name='labs') comment = models.CharField(max_length=30, null=True, blank=True) network = CidrAddressField(null=True, blank=True) objects = NetManager() def __str__(self): return self.building.code + '/' + self.name
class DefaultPool(models.Model): pool = models.ForeignKey( "Pool", related_name="pool_defaults", blank=True, null=True ) cidr = CidrAddressField(unique=True) objects = DefaultPoolManager() def __str__(self): return "%s - %s" % (self.pool, self.cidr) class Meta: db_table = "default_pools"
class UserIpAddress(models.Model): ip = CidrAddressField(unique=True, db_index=True) user = models.ForeignKey(User, related_name='user') objects = NetManager() class Meta: app_label = 'api' db_table = "useripaddress" verbose_name = "User IP Address" verbose_name_plural = "User IP Addresses" def __unicode__(self): return "%s - %s" % (self.ip, self.user)
class Prefix(models.Model): protocol = models.CharField(max_length=8) prefix = CidrAddressField() ixlan_id = models.PositiveIntegerField() objects = NetManager() class Meta: ordering = ["prefix"] verbose_name = "IX Prefix" verbose_name_plural = "IX Prefixes" def __str__(self): return "{} - {}".format(self.protocol, self.prefix)
class PrefixInfo(models.Model): objects = NetManager() TYPE_CHOICES = ((1, "RIR"), (2, "PRIVATE")) asn = models.PositiveIntegerField(blank=True, null=True) prefix = CidrAddressField(unique=True) name = models.CharField(max_length=32) description = models.CharField(max_length=64) country_code = models.CharField(max_length=3) rir = models.CharField(max_length=16, blank=True, null=True) type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return str(self.prefix)
class IXLanPrefix(models.Model): notes = models.CharField(max_length=255, blank=True) protocol = models.CharField(max_length=64, choices=Protocol.choices) prefix = CidrAddressField(unique=True) in_dfz = models.BooleanField(default=False) ixlan = models.ForeignKey( IXLan, default=0, related_name="ixpfx_set", verbose_name="Internet Exchange LAN", on_delete=models.CASCADE, ) class Meta: verbose_name = "Internet Exchange LAN prefix" verbose_name_plural = "Internet Exchange LAN prefixes"
class Network(models.Model): network = CidrAddressField(primary_key=True) name = models.CharField(max_length=255, blank=True, null=True) gateway = InetAddressField(blank=True, null=True) description = models.TextField(blank=True, null=True) vlans = models.ManyToManyField( "Vlan", through="NetworkToVlan", related_name="vlan_networks" ) dhcp_group = models.ForeignKey( "DhcpGroup", db_column="dhcp_group", blank=True, null=True ) shared_network = models.ForeignKey( "SharedNetwork", db_column="shared_network", blank=True, null=True ) changed = models.DateTimeField(auto_now=True) changed_by = models.ForeignKey(settings.AUTH_USER_MODEL, db_column="changed_by") search_index = VectorField() # objects = NetworkQuerySet.as_manager() objects = NetworkManager.from_queryset(NetworkQuerySet)() searcher = SearchManager( fields=("name", "description"), config="pg_catalog.english", # this is default search_field="search_index", # this is default auto_update_search_field=True, ) tags = TaggableManager(through=TaggedNetworks, blank=True) # Forcing pk as string @property def pk(self): return str(self.network) def __str__(self): return "%s" % self.network class Meta: db_table = "networks" permissions = ( ("is_owner_network", "Is owner"), ("add_records_to_network", "Can add records to"), ) default_permissions = ("add", "change", "delete", "view") ordering = ("network",)
def getStartAddress(net: CidrAddressField, size: int) -> int: """Calculate the next possible start address of a network with requested size Arguments: net {CidrAddressField} -- Net to start with size {int} -- subnet size Returns: int -- The actual distance """ if size >= net.prefixlen: startAddress = int(net.broadcast_address) + 1 else: sizeDiff = net.prefixlen - size supernet = net.supernet(sizeDiff) startAddress = int(supernet.network_address) + 2**( supernet.max_prefixlen - supernet.prefixlen) return startAddress
class NetGroupRegexPermission(BaseModel): group = models.CharField(max_length=80) range = CidrAddressField() regex = models.CharField(max_length=250, validators=[validate_regex]) labels = models.ManyToManyField(Label, blank=True, related_name='permissions') objects = NetManager() class Meta: db_table = 'perm_net_group_regex' unique_together = ( 'group', 'range', 'regex', ) def __str__(self): return f"group {self.group}, range {self.range}, regex {self.regex}" @classmethod def find_perm(cls, groups, hostname, ips, require_ip=True): if not isinstance(hostname, str): raise ValueError(f'hostname is invalid type ({type(hostname)})') if isinstance(groups, str): groups = [groups] if not isinstance(groups, (list, tuple)): raise ValueError(f'groups on invalid type ({type(groups)})') if isinstance(ips, str): ips = [ips] if not isinstance(ips, (list, tuple)): raise ValueError(f'ips on invalid type ({type(ips)})') if require_ip and not ips: return cls.objects.none() if not all([groups, hostname]): return cls.objects.none() qs = cls.objects.filter(group__in=groups).extra(where=["%s ~ regex"], params=[str(hostname)]) if require_ip: qs = qs.filter( reduce(lambda x, y: x | y, [Q(range__net_contains=ip) for ip in ips])) return qs
class Ip(BaseAccessLevel): """ IP Address Model """ interface = models.ForeignKey('net.Interface', verbose_name=_('interface')) address = InetAddressField(verbose_name=_('ip address'), unique=True, db_index=True) protocol = models.CharField(_('IP Protocol Version'), max_length=4, choices=IP_PROTOCOLS, default=IP_PROTOCOLS[0][0], blank=True) netmask = CidrAddressField(_('netmask (CIDR, eg: 10.40.0.0/24)')) objects = NetManager() class Meta: app_label = 'net' permissions = (('can_view_ip', 'Can view ip'), ) verbose_name = _('ip address') verbose_name_plural = _('ip addresses') def __unicode__(self): return '%s: %s' % (self.protocol, self.address) def full_clean(self, *args, **kwargs): """ TODO """ pass def save(self, *args, **kwargs): """ Determines ip protocol version automatically """ self.protocol = 'ipv%d' % self.address.version # save super(Ip, self).save(*args, **kwargs) if 'grappelli' in settings.INSTALLED_APPS: @staticmethod def autocomplete_search_fields(): return ('address__icontains', )
class Subnet(models.Model): """ A Subnet represents an IPv4 or IPv6 network, including mask length. Subnets can be assigned to Spaces and VRFs. A Subnet must be assigned a status and may optionally be assigned a used-defined Role. A Subnet can also be assigned to a VLAN where appropriate. """ objects = SubnetManager() name = models.CharField(max_length=50) cidr = CidrAddressField(unique=True) supernet = models.ForeignKey('self', related_name='children', null=True, db_index=True, editable=False) version = models.PositiveSmallIntegerField(choices=AV_CHOICES, editable=False) vrf = models.ForeignKey('ipam.VRF', blank=True, null=True) space = models.ForeignKey('ipam.Space', default=1) vlan = models.ForeignKey('ipam.VLAN', blank=True, null=True) role = models.ManyToManyField('ipam.Role', blank=True) description = models.CharField(max_length=100, blank=True) notes = models.TextField(blank=True) def get_direct_children_subnets_from_cidr(self): descendants = Subnet.objects.subnet_descendants( cidr=self.cidr).order_by('cidr') if not descendants: return descendants roots = [descendants.first()] root = roots[0] for subnet in descendants[1:]: if not subnet.cidr.overlaps(root.cidr): roots.append(subnet) root = subnet return Subnet.objects.filter(pk__in=[root.pk for root in roots]) def save(self, *args, **kwargs): if cache.get('display_with_ancestors'): cache.delete('display_with_ancestors') if isinstance(self.cidr, str): self.cidr = ip_network(self.cidr) self.version = self.cidr.version if not self.pk: # this is a new instance self.supernet = self.find_parent() with transaction.atomic(): super(Subnet, self).save(*args, **kwargs) self.get_direct_children_subnets_from_cidr().update( supernet=self) else: # instance is being updated old_parent = self.supernet self.supernet = self.find_parent() with transaction.atomic(): self.children.update(supernet=old_parent) super(Subnet, self).save(*args, **kwargs) self.get_direct_children_subnets_from_cidr().update( supernet=self) def delete(self, *args, **kwargs): if self.children.count( ) > 0: # if we need to repair the tree, let SubnetManager deal # with it Subnet.objects.cleanup_and_delete(self) else: super(Subnet, self).delete(*args, **kwargs) def __str__(self): return str(self.cidr) def find_parent(self): enclosing_subnets = Subnet.objects.filter( cidr__net_contains=self.cidr).order_by('-cidr') return enclosing_subnets.first() @classmethod def _update_display_with_ancestors_cache(cls): results = dict() for subnet in Subnet.objects.all(): results[subnet.id] = subnet._display_with_ancestors() cache.set('display_with_ancestors', results, timeout=None) def display_with_ancestors(self): cached_values = cache.get('display_with_ancestors') if cached_values: return cached_values[self.id] else: Subnet._update_display_with_ancestors_cache() return cache.get('display_with_ancestors')[self.id] def _display_with_ancestors(self): s = list() parent = self.supernet while parent is not None: s.append(str(parent)) parent = parent.supernet s.reverse() s.append(str(self)) return ' > '.join(s)
class Network(BaseModel): network = CidrAddressField(unique=True) description = models.TextField(blank=True) vlan = models.IntegerField(blank=True, null=True) dns_delegated = models.BooleanField(default=False) category = models.TextField(blank=True) location = models.TextField(blank=True) frozen = models.BooleanField(default=False) reserved = models.PositiveIntegerField(default=3) objects = NetManager() class Meta: db_table = 'network' ordering = ('network', ) def __str__(self): return str(self.network) def save(self, *args, **kwargs): if isinstance(self.network, str): network = ipaddress.ip_network(self.network) else: network = self.network if self.reserved > network.num_addresses: self.reserved = network.num_addresses super().save(*args, **kwargs) def get_reserved_ipaddresses(self): """ Returns a set with the reserved ip addresses for the network.""" network = self.network ret = set([network.network_address]) for i, ip in zip(range(self.reserved), network.hosts()): ret.add(ip) if isinstance(network, ipaddress.IPv4Network): ret.add(network.broadcast_address) return ret def get_excluded_ranges_start_end(self): excluded = [] for start_ip, end_ip in self.excluded_ranges.values_list( 'start_ip', 'end_ip'): start_ip = ipaddress.ip_address(start_ip) end_ip = ipaddress.ip_address(end_ip) excluded.append((start_ip, end_ip)) return excluded def get_unused_ipaddresses(self, max=MAX_UNUSED_LIST): """ Returns which ipaddresses on the network are unused. """ network_ips = [] used_or_reserved = self.used_addresses | self.get_reserved_ipaddresses( ) excluded = self.get_excluded_ranges_start_end() # Getting all available IPs for a ipv6 prefix can easily cause # the webserver to hang due to lots and lots of IPs. Instead limit # to the first MAX_UNUSED_LIST hosts. found = 0 ip = next(self.network.hosts()) while ip in self.network: if ip in used_or_reserved: ip += 1 continue was_excluded = False for start_ip, end_ip in excluded: if ip >= start_ip and ip <= end_ip: ip = end_ip + 1 was_excluded = True if was_excluded: continue network_ips.append(ip) found += 1 if found == max: break ip += 1 return set(network_ips) def __used(self, model): from_ip = str(self.network.network_address) to_ip = str(self.network.broadcast_address) return model.objects.filter(ipaddress__range=(from_ip, to_ip)) @staticmethod def __used_ips(qs): ips = qs.values_list('ipaddress', flat=True) return {ipaddress.ip_address(ip) for ip in ips} def _used_ipaddresses(self): return self.__used(Ipaddress) def _used_ptroverrides(self): return self.__used(PtrOverride) @property def used_ipaddresses(self): """ Returns the used Ipaddress objects on the network. """ return self.__used_ips(self._used_ipaddresses()) @property def used_ptroverrides(self): return self.__used_ips(self._used_ptroverrides()) @property def used_addresses(self): """ Returns which ipaddresses on the network are used. A combined usage of Ipaddress and PtrOverride. """ return self.used_ipaddresses | self.used_ptroverrides @property def unused_addresses(self): """ Returns which ipaddresses on the network are unused. """ return self.get_unused_ipaddresses() @property def unused_count(self): """ Returns the number of unused ipaddresses on the network. """ # start with the number of all adresses defined by the CIDR result = self.network.num_addresses # subtract excluded ranges for i in self.excluded_ranges.all(): result -= i.num_addresses() # subtract used and reserved addresses used_or_reserved = self.used_addresses | self.get_reserved_ipaddresses( ) result -= len(used_or_reserved) return result def get_first_unused(self): """ Return the first unused IP found, if any. """ a = self.get_unused_ipaddresses(1) if a: return str(next(iter(a))) return None def get_random_unused(self): """ Return a random unused IP, if any. """ unused = self.unused_addresses if unused: network = self.network if len( unused ) == MAX_UNUSED_LIST and network.num_addresses > MAX_UNUSED_LIST: # Attempt to use the entire address if encountering a network larger # than MAX_UNUSED_LIST. Typically an IPv6 network. network_address = int(network.network_address) broadcast_address = int(network.broadcast_address) used_or_reserved = self.used_addresses | self.get_reserved_ipaddresses( ) excluded = self.get_excluded_ranges_start_end() # Limit the number of attempts, as random might be really unlucky. for attempts in range(100): choice = random.randint(network_address, broadcast_address) if network.version == 6: randomip = ipaddress.IPv6Address(choice) else: randomip = ipaddress.IPv4Address(choice) if randomip in used_or_reserved: continue was_excluded = False for start_ip, end_ip in excluded: if randomip >= start_ip and randomip <= end_ip: was_excluded = True break if was_excluded: continue return str(randomip) return str(random.choice(tuple(unused))) return None
class ReverseZone(BaseZone): name = DnsNameField(unique=True, validators=[validate_reverse_zone_name]) # network can not be blank, but it will allow full_clean() to pass, even if # the network is not set. Will anyway be overridden by update() and save(). network = CidrAddressField(unique=True, blank=True) objects = NetManager() class Meta: db_table = 'reverse_zone' def save(self, *args, **kwargs): self.network = get_network_from_zonename(self.name) super().save(*args, **kwargs) @staticmethod def get_zone_by_ip(ip): """Search and return a zone which contains an IP address.""" return ReverseZone.objects.filter(network__net_contains=ip).first() def _get_excluded_ranges(self): """ Get ranges which should not be exported in the reverse zone. These are addresses used by sub zones or delegations. Returned as a list of named tuples. """ excluded_ips = list() Range = namedtuple('Range', 'name from_ip to_ip') networks = list() for i in self.delegations.all(): networks.append(get_network_from_zonename(i.name)) for i in ReverseZone.objects.filter(name__endswith="." + self.name): networks.append(i.network) for network in networks: from_ip = str(network.network_address) to_ip = str(network.broadcast_address) excluded_ips.append( Range(name=str(network), from_ip=from_ip, to_ip=to_ip)) return excluded_ips def get_ipaddresses(self): """ Get all ipaddresses used in a reverse zone. Will return tuples of (ipaddress, ttl, hostname), sorted by ipaddress. """ network = self.network from_ip = str(network.network_address) to_ip = str(network.broadcast_address) ipaddresses = dict() override_ips = dict() excluded_ranges = self._get_excluded_ranges() for model, data in ( (Ipaddress, ipaddresses), (PtrOverride, override_ips), ): qs = model.objects.filter(ipaddress__range=(from_ip, to_ip)) for exclude in excluded_ranges: qs = qs.exclude(ipaddress__range=(exclude.from_ip, exclude.to_ip)) for ip, ttl, hostname in qs.values_list('ipaddress', 'host__ttl', 'host__name'): data[ip] = (ttl, hostname) # XXX: send signal/mail to hostmaster(?) about issues with multiple_ip_no_ptr count = defaultdict(int) for i in ipaddresses: if i not in override_ips: count[i] += 1 multiple_ip_no_ptr = {i: count[i] for i in count if count[i] > 1} ptr_done = set() result = [] def _add_to_result(ip, ttl, hostname): # Wildcards are not allowed in reverse zones. if "*" in hostname: return ttl = ttl or "" result.append((ipaddress.ip_address(ip), ttl, hostname)) # Use PtrOverrides when found, but only once. Also skip IPaddresses # which have been used multiple times, but lacks a PtrOverride. for ip, data in ipaddresses.items(): if ip in multiple_ip_no_ptr: continue if ip in override_ips: if ip not in ptr_done: ptr_done.add(ip) _add_to_result(ip, *override_ips[ip]) else: _add_to_result(ip, *data) # Add PtrOverrides which actually don't override anything, # but are only used as PTRs without any Ipaddress object creating # forward entries. for ptr, data in override_ips.items(): if ptr not in ptr_done: _add_to_result(ptr, *data) # Return sorted by IP return sorted(result, key=lambda i: i[0])
class Network(models.Model): network = CidrAddressField(unique=True) description = models.TextField(blank=True) vlan = models.IntegerField(blank=True, null=True) dns_delegated = models.BooleanField(default=False) category = models.TextField(blank=True) location = models.TextField(blank=True) frozen = models.BooleanField(default=False) reserved = models.PositiveIntegerField(default=3) objects = NetManager() class Meta: db_table = 'network' ordering = ('network', ) def __str__(self): return str(self.network) def get_reserved_ipaddresses(self): """ Returns a set with the reserved ip addresses for the network.""" network = self.network ret = set([network.network_address]) for i, ip in zip(range(self.reserved), network.hosts()): ret.add(ip) if isinstance(network, ipaddress.IPv4Network): ret.add(network.broadcast_address) return ret def _get_used_ipaddresses(self): from_ip = str(self.network.network_address) to_ip = str(self.network.broadcast_address) #where_str = "ipaddress BETWEEN '{}' AND '{}'".format(from_ip, to_ip) #ips = Ipaddress.objects.extra(where=[where_str]) return Ipaddress.objects.filter(ipaddress__range=(from_ip, to_ip)) def get_used_ipaddresses(self): """ Returns the used ipaddress on the network. """ ips = self._get_used_ipaddresses() used = {ipaddress.ip_address(i.ipaddress) for i in ips} return used def get_used_ipaddress_count(self): """ Returns the number of used ipaddreses on the network. """ return self._get_used_ipaddresses().count() def get_unused_ipaddresses(self): """ Returns which ip-addresses on the network are unused. """ network_ips = [] if isinstance(self.network, ipaddress.IPv6Network): # Getting all availible IPs for a ipv6 prefix can easily cause # the webserver to hang due to lots and lots of IPs. Instead limit # to the first 4000 hosts. Should probably be configurable. for ip in self.network.hosts(): if len(network_ips) == 4000: break network_ips.append(ip) else: network_ips = self.network.hosts() reserved = self.get_reserved_ipaddresses() used = self.get_used_ipaddresses() return set(network_ips) - reserved - used def get_first_unused(self): """ Return the first unused IP found, if any. """ reserved = self.get_reserved_ipaddresses() used = self.get_used_ipaddresses() for ip in self.network.hosts(): if ip in reserved: continue if ip not in used: return str(ip) return None
class ReverseZone(BaseZone): name = DnsNameField(unique=True, validators=[validate_reverse_zone_name]) # network can not be blank, but it will allow full_clean() to pass, even if # the network is not set. Will anyway be overridden by update() and save(). network = CidrAddressField(unique=True, blank=True) objects = NetManager() class Meta: db_table = 'reverse_zone' def save(self, *args, **kwargs): self.network = get_network_from_zonename(self.name) super().save(*args, **kwargs) @staticmethod def get_zone_by_ip(ip): """Search and return a zone which contains an IP address.""" return ReverseZone.objects.filter(network__net_contains=ip).first() def get_ipaddresses(self): network = self.network from_ip = str(network.network_address) to_ip = str(network.broadcast_address) ips = Ipaddress.objects.filter(ipaddress__range=(from_ip, to_ip)) ips = ips.select_related('host') override_ips = dict() ptrs = PtrOverride.objects.filter(ipaddress__range=(from_ip, to_ip)) ptrs = ptrs.select_related('host') for p in ptrs: override_ips[p.ipaddress] = p # XXX: send signal/mail to hostmaster(?) about issues with multiple_ip_no_ptr count = defaultdict(int) for i in ips: if i.ipaddress not in override_ips: count[i.ipaddress] += 1 multiple_ip_no_ptr = {i: count[i] for i in count if count[i] > 1} ptr_done = set() # Use PtrOverrides when found, but only once. Also skip IPaddresses # which have been used multiple times, but lacks a PtrOverride. result = [] def _add_to_result(item): ttl = item.host.ttl or "" result.append( (ipaddress.ip_address(item.ipaddress), ttl, item.host.name)) for i in ips: ip = i.ipaddress if ip in multiple_ip_no_ptr: continue if ip in override_ips: if ip not in ptr_done: ptr_done.add(ip) _add_to_result(override_ips[ip]) else: _add_to_result(i) # Add PtrOverrides which actually don't override anything, # but are only used as PTRs without any Ipaddress object creating # forward entries. for k, v in override_ips.items(): if k not in ptr_done: _add_to_result(v) # Return sorted by IP return sorted(result, key=lambda i: i[0])
class NetworkIpPool(BaseAbstractModel): network = CidrAddressField( verbose_name=_("Ip network address"), help_text=_("Ip address of network. For example: " "192.168.1.0 or fde8:6789:1234:1::"), unique=True, ) kind = models.PositiveSmallIntegerField( _("Kind of network"), choices=NetworkIpPoolKind.choices, default=NetworkIpPoolKind.NETWORK_KIND_NOT_DEFINED ) description = models.CharField(_("Description"), max_length=64) groups = models.ManyToManyField( Group, verbose_name=_("Member groups"), db_table="networks_ippool_groups", blank=True ) # Usable ip range ip_start = models.GenericIPAddressField(_("Start work ip range")) ip_end = models.GenericIPAddressField(_("End work ip range")) vlan_if = models.ForeignKey( VlanIf, verbose_name=_("Vlan interface"), on_delete=models.CASCADE, blank=True, null=True, default=None ) gateway = models.GenericIPAddressField(_("Gateway ip address")) is_dynamic = models.BooleanField(_("Is dynamic"), default=False) # deprecated: pool_tag is deprecated, remove it pool_tag = models.CharField( _("Tag"), max_length=32, null=True, blank=True, default=None, validators=[validators.validate_slug] ) sites = models.ManyToManyField(Site, blank=True) def __str__(self): return f"{self.description}: {self.network}" def clean(self): errs = {} if self.network is None: errs["network"] = ValidationError(_("Network is invalid"), code="invalid") try: net = ip_network("%s" % self.network) except ValueError as err: errs["network"] = ValidationError(message=str(err), code="invalid") raise ValidationError(errs) if self.ip_start is None: errs["ip_start"] = ValidationError(_("Ip start is invalid"), code="invalid") start_ip = ip_address(self.ip_start) if start_ip not in net: errs["ip_start"] = ValidationError(_("Start ip must be in subnet of specified network"), code="invalid") if self.ip_end is None: errs["ip_end"] = ValidationError(_("Ip end is invalid"), code="invalid") end_ip = ip_address(self.ip_end) if end_ip not in net: errs["ip_end"] = ValidationError(_("End ip must be in subnet of specified network"), code="invalid") gw = ip_address(self.gateway) if gw not in net: errs["gateway"] = ValidationError(_("Gateway ip must be in subnet of specified network"), code="invalid") if start_ip <= gw <= end_ip: errs["gateway"] = ValidationError(_("Gateway must not be in the range of allowed ips"), code="invalid") if errs: raise ValidationError(errs) other_nets = NetworkIpPool.objects.exclude(pk=self.pk).only("network").order_by("network") if not other_nets.exists(): return for other_net in other_nets.iterator(): other_net_netw = ip_network("%s" % other_net.network) if net.overlaps(other_net_netw): errs["network"] = ValidationError( _("Network is overlaps with %(other_network)s"), params={"other_network": str(other_net_netw)} ) raise ValidationError(errs) def get_free_ip(self) -> Optional[Union[IPv4Address, IPv6Address]]: """ Finds unused ip :return: """ with connection.cursor() as cur: cur.execute( "SELECT find_new_ip_pool_lease(%s, %s::boolean, 0::smallint, %s::smallint)" % (self.pk, 1 if self.is_dynamic else 0, self.kind) ) free_ip = cur.fetchone() return ip_address(free_ip[0]) if free_ip and free_ip[0] else None @staticmethod def find_ip_pool_by_ip(ip_addr: str): with connection.cursor() as cur: cur.execute("SELECT * FROM find_ip_pool_by_ip(%s::inet)", (ip_addr,)) res = cur.fetchone() if isinstance(res[0], int) and res[0] > 0: return NetworkIpPool( pk=res[0], network=res[1], kind=res[2], description=res[3], ip_start=res[4], ip_end=res[5], vlan_if_id=res[6], gateway=res[7], is_dynamic=res[8], ) return None class Meta: db_table = "networks_ip_pool" verbose_name = _("Network ip pool") verbose_name_plural = _("Network ip pools") ordering = ("network",)
class UniqueCidrTestModel(Model): field = CidrAddressField(unique=True) objects = NetManager() class Meta: db_table = 'uniquecidr'
class NullCidrTestModel(Model): field = CidrAddressField(null=True) objects = NetManager() class Meta: db_table = 'nullcidr'
class CIDR(MPTTModel, BaseModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) cidr = CidrAddressField(unique=True) parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children') pool = models.ForeignKey('Pool', blank=True, null=True, on_delete=models.DO_NOTHING, related_name='prefixes') fqdn = FQDNField(blank=True, null=True) description = models.TextField(blank=True, null=True) flag = models.CharField( blank=False, max_length=11, choices=[(tag.value, tag.value) for tag in FlagChoices], default=FlagChoices.RESERVATION, ) objects = NetManager() class Meta: ordering = ('cidr', ) @property def supercidr(self) -> 'CIDR': """ :returns: the direct parent of self (by cidr) """ return self.parent @property def subcidr(self) -> List['CIDR']: """ :returns: the direct subcidr of self (by cidr) """ return list(CIDR.objects.filter(parent=self).order_by('cidr').all()) def getChildIDs(self) -> List[str]: """Get the IDs of every child Returns: List[str] -- List of child IDs """ return [ child.id for child in CIDR.objects.filter( parent=self).order_by('cidr').all() ] @property def ips(self) -> List['CIDR']: """ :returns: the direct ips allocated under this prefix """ return [ cidr for cidr in self.get_children() if not utilities.subcidr(cidr.cidr) ] @property def version(self) -> IP: """Returns the version of this object Either IPv4 our IPv6 Returns: IP -- Version (IP.v4 or IP.v6) """ return IP(self.cidr.version) @property def labelDict(self) -> dict: """Get labels as key value pair Returns: dict -- Labels as key-value pair """ return {label.name: label.value for label in self.labels.all()} @transaction.atomic def assignLinknet(self, description: str, hostname=None) -> Tuple['CIDR', 'CIDR', 'CIDR']: """Assigns a new linknet from the pool to be used with physical nodes Arguments: description {str} -- Description of the use for this subnet hostname {str} -- Hostname to use """ if self.version == IP.v4: size = 31 else: size = 127 net = self.assignNet(size, description) # Assign gateway ip manually because network adress = assignment is prohibited by assign net gateway_ip = str(net.cidr.network_address) + "/" + str(size + 1) gateway_ip = ip_network(gateway_ip) gateway = CIDR(cidr=gateway_ip, description='Gateway', flag=FlagChoices.ASSIGNMENT, parent=net) gateway.save() # Now use assignIP to not do the assign workaround again ip = net.assignIP(description, hostname) return net, gateway, ip def assignIP(self, description: str, hostname=None) -> 'CIDR': """Assigns a new single ip to be used for VMs Arguments: description {str} -- Description of the use for this subnet hostname {str} -- Hostname to use """ if self.version == IP.v4: size = 32 else: size = 128 return self.assignNet(size, description, hostname, flag=FlagChoices.HOST) def assignNet(self, size: int, description: str, hostname=None, flag=FlagChoices.RESERVATION, offset=1) -> 'CIDR': """Assign a subnet of requested size from this network Arguments: size {int} -- Desired size of the subnet description {str} -- Description of the use for this subnet Keyword Arguments: hostname {[type]} -- Hostname to use (default: {None}) flag: {FlagChoices} -- Network type (reservation, assignment, host) offset: {int} -- Offset from the network address (e.g. to allow network address assignments) """ def getStartAddress(net: CidrAddressField, size: int) -> int: """Calculate the next possible start address of a network with requested size Arguments: net {CidrAddressField} -- Net to start with size {int} -- subnet size Returns: int -- The actual distance """ if size >= net.prefixlen: startAddress = int(net.broadcast_address) + 1 else: sizeDiff = net.prefixlen - size supernet = net.supernet(sizeDiff) startAddress = int(supernet.network_address) + 2**( supernet.max_prefixlen - supernet.prefixlen) return startAddress if size <= self.cidr.prefixlen: raise NotEnoughSpace gapSize = 2**(self.cidr.max_prefixlen - size) # When the network is empty, take first if len(self.subcidr) == 0: # Make use of ipaddress functions to get all subnets of requested size subnets = self.cidr.subnets(new_prefix=size) next(subnets) newNet = next(subnets) # Instantiate new object and persist newCIDR = CIDR(cidr=newNet, description=description, fqdn=hostname, flag=flag, parent=self) newCIDR.save() return newCIDR # Non empty network --> find best place smallestGap = None # Check whether there is enough space before the first child firstChild = self.subcidr[0] firstGap = int( firstChild.cidr.network_address) - int(self.cidr.network_address + offset) if firstGap >= gapSize: smallestGap = { 'address': self.cidr.network_address + offset, 'length': firstGap } for i, child in enumerate(self.subcidr[:-1]): net1 = child.cidr net2 = self.subcidr[i + 1].cidr startAddress = getStartAddress(net1, size) gap = int(net2.network_address) - startAddress if gap >= gapSize and (smallestGap is None or gap < smallestGap['length']): smallestGap = { 'address': ip_address(startAddress), 'length': gap } # the last gap lastBlock = self.subcidr[-1].cidr lastGapStart = getStartAddress(lastBlock, size) lastGap = int(self.cidr.broadcast_address) - lastGapStart + 1 if lastGap >= gapSize and (smallestGap is None or lastGap < smallestGap['length']): smallestGap = { 'address': ip_address(lastGapStart), 'length': lastGap } if smallestGap is None: raise NotEnoughSpace # This is ugly but apparently IPVXNetwork has no way to change the netmask newNet = str(smallestGap['address']) + "/" + str(size) newNet = ip_network(newNet) # Instantiate new object and persist newCIDR = CIDR(cidr=newNet, description=description, fqdn=hostname, flag=flag, parent=self) newCIDR.save() return newCIDR
class WhitelistEntry(models.Model): cidr = CidrAddressField() who = models.ForeignKey(User) why = models.TextField() added = models.DateTimeField('date added', auto_now_add=True)
class Block(models.Model): FLAG_DIRECTIONS = ( (FLAG_NONE, 'None'), (FLAG_INBOUND, 'Inbound'), (FLAG_OUTBOUND, 'Outbound'), (FLAG_BOTH, 'Both'), ) cidr = CidrAddressField(db_index=True) who = models.ForeignKey(User) source = models.CharField(max_length=30, db_index=True) why = models.TextField() added = models.DateTimeField('date added', auto_now_add=True) unblock_at = models.DateTimeField('date to be unblocked', null=True, db_index=True) flag = models.CharField(max_length=1, choices=FLAG_DIRECTIONS, default=FLAG_NONE) skip_whitelist = models.BooleanField(default=False) forced_unblock = models.BooleanField(default=False, db_index=True) unblock_why = models.TextField(blank=True) unblock_who = models.ForeignKey(User, related_name='+', null=True, blank=True) objects = models.Manager() current = CurrentBlockManager() expected = ExpectedBlockManager() pending = PendingBlockManager() pending_removal = PendingRemovalBlockManager() expired = ExpiredBlockManager() def save(self, *args, **kwargs): if not self.skip_whitelist and not self.unblock_now: wle = is_whitelisted(self.cidr) if wle: raise WhitelistError(wle.why) if is_prefixlen_too_small(self.cidr): raise PrefixLenTooSmallError("Prefix length in %s is too small" % self.cidr) item = is_source_blacklisted(self.source) if item: raise SourceBlacklistedError("Source %s is blacklisted: %s: %s" % (self.source, item.who, item.why)) super(Block, self).save(*args, **kwargs) @property def is_unblockable(self): """Is this block record unblockable? This is not the same as if it IS blocked, but more "should this be blocked" """ if self.forced_unblock: return False if self.unblock_at is None: return True if self.unblock_at > timezone.now(): return True return False @property def duration(self): if self.unblock_at is None: return None return self.unblock_at - self.added @property def age(self): if self.unblock_at is None: return None return timezone.now() - self.unblock_at def unblock_now(self, who, why): self.forced_unblock = True self.unblock_who = who self.unblock_why = why self.unblock_at = timezone.now() self.save()
class Ip(BaseAccessLevel): """ IP Address Model """ interface = models.ForeignKey('net.Interface', verbose_name=_('interface')) address = InetAddressField(verbose_name=_('ip address'), unique=True, db_index=True) protocol = models.CharField(_('IP Protocol Version'), max_length=4, choices=IP_PROTOCOLS, default=IP_PROTOCOLS[0][0], blank=True) netmask = CidrAddressField(_('netmask (CIDR, eg: 10.40.0.0/24)'), blank=True, null=True) objects = NetAccessLevelManager() class Meta: app_label = 'net' permissions = (('can_view_ip', 'Can view ip'), ) verbose_name = _('ip address') verbose_name_plural = _('ip addresses') def __unicode__(self): return '%s: %s' % (self.protocol, self.address) def clean(self, *args, **kwargs): """ TODO """ # netaddr.IPAddress('10.40.2.1') in netaddr.IPNetwork('10.40.0.0/24') pass def save(self, *args, **kwargs): """ Determines ip protocol version automatically. Stores address in interface shortcuts for convenience. """ self.protocol = 'ipv%d' % self.address.version # save super(Ip, self).save(*args, **kwargs) # save shortcut on interfaces ip_cached_list = self.interface.ip_addresses # if not present in interface shorctus add it to the list if str(self.address) not in ip_cached_list: # recalculate cached_ip_list recalculated_ip_cached_list = [] for ip in self.interface.ip_set.all(): recalculated_ip_cached_list.append(str(ip.address)) # rebuild string in format "<ip_1>, <ip_2>" self.interface.ip_addresses = recalculated_ip_cached_list self.interface.save() @property def owner(self): return self.interface.owner if 'grappelli' in settings.INSTALLED_APPS: @staticmethod def autocomplete_search_fields(): return ('address__icontains', )
class CidrTestModel(Model): field = CidrAddressField() objects = NetManager() class Meta: db_table = 'cidr'