Esempio n. 1
0
File: ip.py Progetto: zhyh329/netbox
class Aggregate(PrimaryModel):
    """
    An aggregate exists at the root level of the IP address space hierarchy in NetBox. Aggregates are used to organize
    the hierarchy and track the overall utilization of available address space. Each Aggregate is assigned to a RIR.
    """
    prefix = IPNetworkField()
    rir = models.ForeignKey(to='ipam.RIR',
                            on_delete=models.PROTECT,
                            related_name='aggregates',
                            verbose_name='RIR')
    tenant = models.ForeignKey(to='tenancy.Tenant',
                               on_delete=models.PROTECT,
                               related_name='aggregates',
                               blank=True,
                               null=True)
    date_added = models.DateField(blank=True, null=True)
    description = models.CharField(max_length=200, blank=True)

    objects = RestrictedQuerySet.as_manager()

    csv_headers = ['prefix', 'rir', 'tenant', 'date_added', 'description']
    clone_fields = [
        'rir',
        'tenant',
        'date_added',
        'description',
    ]

    class Meta:
        ordering = ('prefix', 'pk')  # prefix may be non-unique

    def __str__(self):
        return str(self.prefix)

    def get_absolute_url(self):
        return reverse('ipam:aggregate', args=[self.pk])

    def clean(self):
        super().clean()

        if self.prefix:

            # Clear host bits from prefix
            self.prefix = self.prefix.cidr

            # /0 masks are not acceptable
            if self.prefix.prefixlen == 0:
                raise ValidationError(
                    {'prefix': "Cannot create aggregate with /0 mask."})

            # Ensure that the aggregate being added is not covered by an existing aggregate
            covering_aggregates = Aggregate.objects.filter(
                prefix__net_contains_or_equals=str(self.prefix))
            if self.pk:
                covering_aggregates = covering_aggregates.exclude(pk=self.pk)
            if covering_aggregates:
                raise ValidationError({
                    'prefix':
                    "Aggregates cannot overlap. {} is already covered by an existing aggregate ({})."
                    .format(self.prefix, covering_aggregates[0])
                })

            # Ensure that the aggregate being added does not cover an existing aggregate
            covered_aggregates = Aggregate.objects.filter(
                prefix__net_contained=str(self.prefix))
            if self.pk:
                covered_aggregates = covered_aggregates.exclude(pk=self.pk)
            if covered_aggregates:
                raise ValidationError({
                    'prefix':
                    "Aggregates cannot overlap. {} covers an existing aggregate ({})."
                    .format(self.prefix, covered_aggregates[0])
                })

    def to_csv(self):
        return (
            self.prefix,
            self.rir.name,
            self.tenant.name if self.tenant else None,
            self.date_added,
            self.description,
        )

    @property
    def family(self):
        if self.prefix:
            return self.prefix.version
        return None

    def get_utilization(self):
        """
        Determine the prefix utilization of the aggregate and return it as a percentage.
        """
        queryset = Prefix.objects.filter(
            prefix__net_contained_or_equal=str(self.prefix))
        child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
        return int(float(child_prefixes.size) / self.prefix.size * 100)
Esempio n. 2
0
File: ip.py Progetto: zhyh329/netbox
class Prefix(PrimaryModel):
    """
    A Prefix represents an IPv4 or IPv6 network, including mask length. Prefixes can optionally be assigned to Sites and
    VRFs. A Prefix must be assigned a status and may optionally be assigned a used-define Role. A Prefix can also be
    assigned to a VLAN where appropriate.
    """
    prefix = IPNetworkField(help_text='IPv4 or IPv6 network with mask')
    site = models.ForeignKey(to='dcim.Site',
                             on_delete=models.PROTECT,
                             related_name='prefixes',
                             blank=True,
                             null=True)
    vrf = models.ForeignKey(to='ipam.VRF',
                            on_delete=models.PROTECT,
                            related_name='prefixes',
                            blank=True,
                            null=True,
                            verbose_name='VRF')
    tenant = models.ForeignKey(to='tenancy.Tenant',
                               on_delete=models.PROTECT,
                               related_name='prefixes',
                               blank=True,
                               null=True)
    vlan = models.ForeignKey(to='ipam.VLAN',
                             on_delete=models.PROTECT,
                             related_name='prefixes',
                             blank=True,
                             null=True,
                             verbose_name='VLAN')
    status = models.CharField(max_length=50,
                              choices=PrefixStatusChoices,
                              default=PrefixStatusChoices.STATUS_ACTIVE,
                              verbose_name='Status',
                              help_text='Operational status of this prefix')
    role = models.ForeignKey(to='ipam.Role',
                             on_delete=models.SET_NULL,
                             related_name='prefixes',
                             blank=True,
                             null=True,
                             help_text='The primary function of this prefix')
    is_pool = models.BooleanField(
        verbose_name='Is a pool',
        default=False,
        help_text='All IP addresses within this prefix are considered usable')
    description = models.CharField(max_length=200, blank=True)

    objects = PrefixQuerySet.as_manager()

    csv_headers = [
        'prefix',
        'vrf',
        'tenant',
        'site',
        'vlan_group',
        'vlan',
        'status',
        'role',
        'is_pool',
        'description',
    ]
    clone_fields = [
        'site',
        'vrf',
        'tenant',
        'vlan',
        'status',
        'role',
        'is_pool',
        'description',
    ]

    class Meta:
        ordering = (F('vrf').asc(nulls_first=True), 'prefix', 'pk'
                    )  # (vrf, prefix) may be non-unique
        verbose_name_plural = 'prefixes'

    def __str__(self):
        return str(self.prefix)

    def get_absolute_url(self):
        return reverse('ipam:prefix', args=[self.pk])

    def clean(self):
        super().clean()

        if self.prefix:

            # /0 masks are not acceptable
            if self.prefix.prefixlen == 0:
                raise ValidationError(
                    {'prefix': "Cannot create prefix with /0 mask."})

            # Disallow host masks
            if self.prefix.version == 4 and self.prefix.prefixlen == 32:
                raise ValidationError({
                    'prefix':
                    "Cannot create host addresses (/32) as prefixes. Create an IPv4 address instead."
                })
            elif self.prefix.version == 6 and self.prefix.prefixlen == 128:
                raise ValidationError({
                    'prefix':
                    "Cannot create host addresses (/128) as prefixes. Create an IPv6 address instead."
                })

            # Enforce unique IP space (if applicable)
            if (self.vrf is None and settings.ENFORCE_GLOBAL_UNIQUE) or (
                    self.vrf and self.vrf.enforce_unique):
                duplicate_prefixes = self.get_duplicates()
                if duplicate_prefixes:
                    raise ValidationError({
                        'prefix':
                        "Duplicate prefix found in {}: {}".format(
                            "VRF {}".format(self.vrf)
                            if self.vrf else "global table",
                            duplicate_prefixes.first(),
                        )
                    })

    def save(self, *args, **kwargs):

        if isinstance(self.prefix, netaddr.IPNetwork):

            # Clear host bits from prefix
            self.prefix = self.prefix.cidr

        super().save(*args, **kwargs)

    def to_csv(self):
        return (
            self.prefix,
            self.vrf.name if self.vrf else None,
            self.tenant.name if self.tenant else None,
            self.site.name if self.site else None,
            self.vlan.group.name if self.vlan and self.vlan.group else None,
            self.vlan.vid if self.vlan else None,
            self.get_status_display(),
            self.role.name if self.role else None,
            self.is_pool,
            self.description,
        )

    @property
    def family(self):
        if self.prefix:
            return self.prefix.version
        return None

    def _set_prefix_length(self, value):
        """
        Expose the IPNetwork object's prefixlen attribute on the parent model so that it can be manipulated directly,
        e.g. for bulk editing.
        """
        if self.prefix is not None:
            self.prefix.prefixlen = value

    prefix_length = property(fset=_set_prefix_length)

    def get_status_class(self):
        return PrefixStatusChoices.CSS_CLASSES.get(self.status)

    def get_duplicates(self):
        return Prefix.objects.filter(vrf=self.vrf, prefix=str(
            self.prefix)).exclude(pk=self.pk)

    def get_child_prefixes(self):
        """
        Return all Prefixes within this Prefix and VRF. If this Prefix is a container in the global table, return child
        Prefixes belonging to any VRF.
        """
        if self.vrf is None and self.status == PrefixStatusChoices.STATUS_CONTAINER:
            return Prefix.objects.filter(
                prefix__net_contained=str(self.prefix))
        else:
            return Prefix.objects.filter(prefix__net_contained=str(
                self.prefix),
                                         vrf=self.vrf)

    def get_child_ips(self):
        """
        Return all IPAddresses within this Prefix and VRF. If this Prefix is a container in the global table, return
        child IPAddresses belonging to any VRF.
        """
        if self.vrf is None and self.status == PrefixStatusChoices.STATUS_CONTAINER:
            return IPAddress.objects.filter(
                address__net_host_contained=str(self.prefix))
        else:
            return IPAddress.objects.filter(address__net_host_contained=str(
                self.prefix),
                                            vrf=self.vrf)

    def get_available_prefixes(self):
        """
        Return all available Prefixes within this prefix as an IPSet.
        """
        prefix = netaddr.IPSet(self.prefix)
        child_prefixes = netaddr.IPSet(
            [child.prefix for child in self.get_child_prefixes()])
        available_prefixes = prefix - child_prefixes

        return available_prefixes

    def get_available_ips(self):
        """
        Return all available IPs within this prefix as an IPSet.
        """
        prefix = netaddr.IPSet(self.prefix)
        child_ips = netaddr.IPSet(
            [ip.address.ip for ip in self.get_child_ips()])
        available_ips = prefix - child_ips

        # IPv6, pool, or IPv4 /31 sets are fully usable
        if self.family == 6 or self.is_pool or self.prefix.prefixlen == 31:
            return available_ips

        # For "normal" IPv4 prefixes, omit first and last addresses
        available_ips -= netaddr.IPSet([
            netaddr.IPAddress(self.prefix.first),
            netaddr.IPAddress(self.prefix.last),
        ])

        return available_ips

    def get_first_available_prefix(self):
        """
        Return the first available child prefix within the prefix (or None).
        """
        available_prefixes = self.get_available_prefixes()
        if not available_prefixes:
            return None
        return available_prefixes.iter_cidrs()[0]

    def get_first_available_ip(self):
        """
        Return the first available IP within the prefix (or None).
        """
        available_ips = self.get_available_ips()
        if not available_ips:
            return None
        return '{}/{}'.format(next(available_ips.__iter__()),
                              self.prefix.prefixlen)

    def get_utilization(self):
        """
        Determine the utilization of the prefix and return it as a percentage. For Prefixes with a status of
        "container", calculate utilization based on child prefixes. For all others, count child IP addresses.
        """
        if self.status == PrefixStatusChoices.STATUS_CONTAINER:
            queryset = Prefix.objects.filter(prefix__net_contained=str(
                self.prefix),
                                             vrf=self.vrf)
            child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
            return int(float(child_prefixes.size) / self.prefix.size * 100)
        else:
            # Compile an IPSet to avoid counting duplicate IPs
            child_count = netaddr.IPSet(
                [ip.address.ip for ip in self.get_child_ips()]).size
            prefix_size = self.prefix.size
            if self.prefix.version == 4 and self.prefix.prefixlen < 31 and not self.is_pool:
                prefix_size -= 2
            return int(float(child_count) / prefix_size * 100)
Esempio n. 3
0
class ReverseZone(models.Model):
    prefix = IPNetworkField(
        verbose_name=_('prefix'),
        unique=True,
    )
    name = models.CharField(
        verbose_name=_('reverse zone name'),
        max_length=255,
        blank=True,
        help_text=
        _("RFC 2317 style reverse DNS, required when the prefix doesn't map to a reverse zone"
          ),
    )
    ttl = models.PositiveIntegerField(verbose_name=_('TTL'), )
    server = models.ForeignKey(
        to=Server,
        verbose_name=_('DDNS Server'),
        on_delete=models.PROTECT,
    )

    objects = ReverseZoneQuerySet.as_manager()

    class Meta:
        ordering = ('prefix', )
        verbose_name = _('reverse zone')
        verbose_name_plural = _('reverse zones')

    def __str__(self):
        return f'for {self.prefix}'

    def record_name(self, address: ip.IPAddress):
        record_name = self.name
        if IPNetwork(self.prefix).version == 4:
            for pos, octet in enumerate(address.words):
                if (pos + 1) * 8 <= self.prefix.prefixlen:
                    continue

                record_name = f'{octet}.{record_name}'
        else:
            nibbles = f'{address.value:032x}'
            for pos, nibble in enumerate(nibbles):
                if (pos + 1) * 4 <= self.prefix.prefixlen:
                    continue

                record_name = f'{nibble}.{record_name}'

        return record_name

    def clean(self):
        if isinstance(self.prefix, IPNetwork) and self.prefix.version == 4:
            if self.prefix.prefixlen not in [0, 8, 16, 24] and not self.name:
                raise ValidationError({
                    'name':
                    _('Required when prefix length is not 0, 8, 16 or 24'),
                })
            elif not self.name:
                # Generate it for the user
                self.name = 'in-addr.arpa'
                for pos, octet in enumerate(self.prefix.ip.words):
                    if pos * 8 >= self.prefix.prefixlen:
                        break

                    self.name = f'{octet}.{self.name}'

        elif isinstance(self.prefix, IPNetwork) and self.prefix.version == 6:
            if self.prefix.prefixlen % 4 != 0 and not self.name:
                raise ValidationError({
                    'name':
                    _('Required when prefix length is not a nibble boundary'),
                })
            elif not self.name:
                # Generate it for the user
                self.name = 'ip6.arpa'
                nibbles = f'{self.prefix.ip.value:032x}'
                for pos, nibble in enumerate(nibbles):
                    if pos * 4 >= self.prefix.prefixlen:
                        break

                    self.name = f'{nibble}.{self.name}'

        # Ensure trailing dots from domain-style fields
        self.name = normalize_fqdn(self.name)
Esempio n. 4
0
class DiscoveryRequest(ChangeLoggedModel):
    prefix = IPNetworkField()
    update_existing = models.BooleanField(
        default=False
    )
    status = models.PositiveSmallIntegerField(
        choices=REQUEST_STATUS_CHOICES,
        default=REQUEST_STATUS_PENDING
    )
    platform = models.ForeignKey(
        to=Platform,
        related_name='+',
        on_delete=models.CASCADE
    )
    job = models.CharField(
        max_length=50,
        null=True,
        blank=True
    )
    site = models.ForeignKey(
        to=Site,
        related_name='+',
        on_delete=models.CASCADE
    )
    device_role = models.ForeignKey(
        to=DeviceRole,
        related_name='+',
        on_delete=models.CASCADE
    )

    def __str__(self):
        return '{} {}'.format(self.prefix, self.created)

    def get_absolute_url(self):
        return reverse('np_autodiscovery:discoveryrequest', args=[self.pk])

    def get_status_class(self):
        return REQUEST_STATUS_CLASSES[self.status]

    def save(self, *args, **kwargs):
        if not self.pk:
            new = True
        else:
            new = False

        super().save(*args, **kwargs)

        if new:
            discovery_queue = get_queue('default')
            job = discovery_queue.enqueue(
                "np_autodiscovery.worker.discovery_job",
                self
            )
            self.job = str(job.key)
            self.save()

    def delete(self, *args, **kwargs):
        if self.job:
            try:
                cancel_job(self.job)
            except Exception:
                pass
        super().delete(*args, **kwargs)
Esempio n. 5
0
class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
    """
    A Prefix represents an IPv4 or IPv6 network, including mask length. Prefixes can optionally be assigned to Sites and
    VRFs. A Prefix must be assigned a status and may optionally be assigned a used-define Role. A Prefix can also be
    assigned to a VLAN where appropriate.
    """
    prefix = IPNetworkField(help_text='IPv4 or IPv6 network with mask')
    site = models.ForeignKey(to='dcim.Site',
                             on_delete=models.PROTECT,
                             related_name='prefixes',
                             blank=True,
                             null=True)
    vrf = models.ForeignKey(to='ipam.VRF',
                            on_delete=models.PROTECT,
                            related_name='prefixes',
                            blank=True,
                            null=True,
                            verbose_name='VRF')
    tenant = models.ForeignKey(to='tenancy.Tenant',
                               on_delete=models.PROTECT,
                               related_name='prefixes',
                               blank=True,
                               null=True)
    vlan = models.ForeignKey(to='ipam.VLAN',
                             on_delete=models.PROTECT,
                             related_name='prefixes',
                             blank=True,
                             null=True,
                             verbose_name='VLAN')
    status = models.CharField(max_length=50,
                              choices=PrefixStatusChoices,
                              default=PrefixStatusChoices.STATUS_ACTIVE,
                              verbose_name='Status',
                              help_text='Operational status of this prefix')
    role = models.ForeignKey(to='ipam.Role',
                             on_delete=models.SET_NULL,
                             related_name='prefixes',
                             blank=True,
                             null=True,
                             help_text='The primary function of this prefix')
    is_pool = models.BooleanField(
        verbose_name='Is a pool',
        default=False,
        help_text='All IP addresses within this prefix are considered usable')
    mark_utilized = models.BooleanField(default=False,
                                        help_text="Treat as 100% utilized")
    description = models.CharField(max_length=200, blank=True)

    # Cached depth & child counts
    _depth = models.PositiveSmallIntegerField(default=0, editable=False)
    _children = models.PositiveBigIntegerField(default=0, editable=False)

    objects = PrefixQuerySet.as_manager()

    clone_fields = [
        'site',
        'vrf',
        'tenant',
        'vlan',
        'status',
        'role',
        'is_pool',
        'mark_utilized',
        'description',
    ]

    class Meta:
        ordering = (F('vrf').asc(nulls_first=True), 'prefix', 'pk'
                    )  # (vrf, prefix) may be non-unique
        verbose_name_plural = 'prefixes'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Cache the original prefix and VRF so we can check if they have changed on post_save
        self._prefix = self.prefix
        self._vrf = self.vrf

    def __str__(self):
        return str(self.prefix)

    def get_absolute_url(self):
        return reverse('ipam:prefix', args=[self.pk])

    def clean(self):
        super().clean()

        if self.prefix:

            # /0 masks are not acceptable
            if self.prefix.prefixlen == 0:
                raise ValidationError(
                    {'prefix': "Cannot create prefix with /0 mask."})

            # Enforce unique IP space (if applicable)
            if (self.vrf is None and get_config().ENFORCE_GLOBAL_UNIQUE) or (
                    self.vrf and self.vrf.enforce_unique):
                duplicate_prefixes = self.get_duplicates()
                if duplicate_prefixes:
                    raise ValidationError({
                        'prefix':
                        "Duplicate prefix found in {}: {}".format(
                            "VRF {}".format(self.vrf)
                            if self.vrf else "global table",
                            duplicate_prefixes.first(),
                        )
                    })

    def save(self, *args, **kwargs):

        if isinstance(self.prefix, netaddr.IPNetwork):

            # Clear host bits from prefix
            self.prefix = self.prefix.cidr

        super().save(*args, **kwargs)

    @property
    def family(self):
        return self.prefix.version if self.prefix else None

    @property
    def mask_length(self):
        return self.prefix.prefixlen if self.prefix else None

    @property
    def depth(self):
        return self._depth

    @property
    def children(self):
        return self._children

    def _set_prefix_length(self, value):
        """
        Expose the IPNetwork object's prefixlen attribute on the parent model so that it can be manipulated directly,
        e.g. for bulk editing.
        """
        if self.prefix is not None:
            self.prefix.prefixlen = value

    prefix_length = property(fset=_set_prefix_length)

    def get_status_class(self):
        return PrefixStatusChoices.CSS_CLASSES.get(self.status)

    def get_parents(self, include_self=False):
        """
        Return all containing Prefixes in the hierarchy.
        """
        lookup = 'net_contains_or_equals' if include_self else 'net_contains'
        return Prefix.objects.filter(**{
            'vrf': self.vrf,
            f'prefix__{lookup}': self.prefix
        })

    def get_children(self, include_self=False):
        """
        Return all covered Prefixes in the hierarchy.
        """
        lookup = 'net_contained_or_equal' if include_self else 'net_contained'
        return Prefix.objects.filter(**{
            'vrf': self.vrf,
            f'prefix__{lookup}': self.prefix
        })

    def get_duplicates(self):
        return Prefix.objects.filter(vrf=self.vrf, prefix=str(
            self.prefix)).exclude(pk=self.pk)

    def get_child_prefixes(self):
        """
        Return all Prefixes within this Prefix and VRF. If this Prefix is a container in the global table, return child
        Prefixes belonging to any VRF.
        """
        if self.vrf is None and self.status == PrefixStatusChoices.STATUS_CONTAINER:
            return Prefix.objects.filter(
                prefix__net_contained=str(self.prefix))
        else:
            return Prefix.objects.filter(prefix__net_contained=str(
                self.prefix),
                                         vrf=self.vrf)

    def get_child_ranges(self):
        """
        Return all IPRanges within this Prefix and VRF.
        """
        return IPRange.objects.filter(
            vrf=self.vrf,
            start_address__net_host_contained=str(self.prefix),
            end_address__net_host_contained=str(self.prefix))

    def get_child_ips(self):
        """
        Return all IPAddresses within this Prefix and VRF. If this Prefix is a container in the global table, return
        child IPAddresses belonging to any VRF.
        """
        if self.vrf is None and self.status == PrefixStatusChoices.STATUS_CONTAINER:
            return IPAddress.objects.filter(
                address__net_host_contained=str(self.prefix))
        else:
            return IPAddress.objects.filter(address__net_host_contained=str(
                self.prefix),
                                            vrf=self.vrf)

    def get_available_ips(self):
        """
        Return all available IPs within this prefix as an IPSet.
        """
        if self.mark_utilized:
            return list()

        prefix = netaddr.IPSet(self.prefix)
        child_ips = netaddr.IPSet(
            [ip.address.ip for ip in self.get_child_ips()])
        child_ranges = netaddr.IPSet()
        for iprange in self.get_child_ranges():
            child_ranges.add(iprange.range)
        available_ips = prefix - child_ips - child_ranges

        # IPv6, pool, or IPv4 /31-/32 sets are fully usable
        if self.family == 6 or self.is_pool or (self.family == 4 and
                                                self.prefix.prefixlen >= 31):
            return available_ips

        # For "normal" IPv4 prefixes, omit first and last addresses
        available_ips -= netaddr.IPSet([
            netaddr.IPAddress(self.prefix.first),
            netaddr.IPAddress(self.prefix.last),
        ])

        return available_ips

    def get_first_available_ip(self):
        """
        Return the first available IP within the prefix (or None).
        """
        available_ips = self.get_available_ips()
        if not available_ips:
            return None
        return '{}/{}'.format(next(available_ips.__iter__()),
                              self.prefix.prefixlen)

    def get_utilization(self):
        """
        Determine the utilization of the prefix and return it as a percentage. For Prefixes with a status of
        "container", calculate utilization based on child prefixes. For all others, count child IP addresses.
        """
        if self.mark_utilized:
            return 100

        if self.status == PrefixStatusChoices.STATUS_CONTAINER:
            queryset = Prefix.objects.filter(prefix__net_contained=str(
                self.prefix),
                                             vrf=self.vrf)
            child_prefixes = netaddr.IPSet([p.prefix for p in queryset])
            utilization = int(
                float(child_prefixes.size) / self.prefix.size * 100)
        else:
            # Compile an IPSet to avoid counting duplicate IPs
            child_ips = netaddr.IPSet(
                [_.range for _ in self.get_child_ranges()] +
                [_.address.ip for _ in self.get_child_ips()])

            prefix_size = self.prefix.size
            if self.prefix.version == 4 and self.prefix.prefixlen < 31 and not self.is_pool:
                prefix_size -= 2
            utilization = int(float(child_ips.size) / prefix_size * 100)

        return min(utilization, 100)