示例#1
0
class AggregateTestChildModel(Model):
    parent = ForeignKey(
        'AggregateTestModel',
        related_name='children',
        on_delete=CASCADE,
    )
    network = CidrAddressField()
    inet = InetAddressField()
示例#2
0
class NetworkIXLan(models.Model):
    asn = ASNField(verbose_name="ASN")
    ipaddr4 = InetAddressField(
        verbose_name="IPv4",
        validators=[AddressFamilyValidator(4)],
        blank=True,
        null=True,
    )
    ipaddr6 = InetAddressField(
        verbose_name="IPv6",
        validators=[AddressFamilyValidator(6)],
        blank=True,
        null=True,
    )
    is_rs_peer = models.BooleanField(default=False)
    notes = models.CharField(max_length=255, blank=True)
    speed = models.PositiveIntegerField()
    operational = models.BooleanField(default=True)
    net = models.ForeignKey(
        Network,
        default=0,
        related_name="netixlan_set",
        verbose_name="Network",
        on_delete=models.CASCADE,
    )
    ixlan = models.ForeignKey(
        IXLan,
        default=0,
        related_name="netixlan_set",
        verbose_name="Internet Exchange LAN",
        on_delete=models.CASCADE,
    )

    ignored_fields = ["ix_id", "name"]

    class Meta:
        verbose_name = "Public Peering Exchange Point"
        verbose_name_plural = "Public Peering Exchange Points"
示例#3
0
class UnknownScanConflict(ScanConflict):
    ip = InetAddressField()
    mac = MACAddressField()
    lab = models.ForeignKey(Lab, on_delete=models.SET_NULL, null=True, blank=True)
    lastseen = models.DateTimeField(default=timezone.now)

    def safe_OUI_org(self):
        try:
            return self.mac.info['OUI']['org']
        except NotRegisteredError:
            return "unknown"

    def __str__(self):
        return f"[unknown conflict] {self.mac} with IP {self.ip} firstseen at {self.scan.timestamp}"
示例#4
0
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",)
示例#5
0
class Tunnel(Interface):
    """ Tunnel Interface """
    sap = models.CharField(max_length=10, null=True, blank=True)
    protocol = models.CharField(max_length=10, help_text=_('eg: GRE'), blank=True, null=True)
    endpoint = InetAddressField(verbose_name=_('end point'), blank=True, null=True)
    
    objects = NetAccessLevelManager()
    
    class Meta:
        app_label = 'net'
        db_table = 'net_interface_tunnel'
        verbose_name = _('tunnel interface')
        verbose_name_plural = _('tunnel interfaces')
    
    def save(self, *args, **kwargs):
        """ automatically set Interface.type to tunnel """
        self.type = INTERFACE_TYPES.get('tunnel')
        super(Tunnel, self).save(*args, **kwargs)
示例#6
0
class VirtualHostsScan(models.Model):
    scan = models.ForeignKey(ScanSession, on_delete=models.CASCADE, related_name='virtualhosts')
    status = models.CharField(
        max_length=8,
        choices=[
            ('active', 'Active'),
            ('old', 'Old'),
            ('ignore', 'Ignored'),
            ('super', 'Superseded')
        ],
        null=False,
        default='active'
    )
    ip = InetAddressField()
    mac = MACAddressField()
    lab = models.ForeignKey(Lab, on_delete=models.SET_NULL, null=True, blank=True)
    lastseen = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return f"Virtual MAC: {self.mac}/{self.ip} in {self.lab}"
示例#7
0
class IP(models.Model):
    # https://docs.python.org/3/library/ipaddress.html
    # inet = InetAddressField(primary_key=True)
    inet = InetAddressField()

    open_ports = ArrayField(models.IntegerField(), blank=True, null=True)

    objects = NetManager()

    class Meta:
        verbose_name = _('IP')
        verbose_name_plural = _('IP-addresses')

    @classmethod
    def stat(cls):
        """Return Port and how many IPs have it open"""
        return cls.objects \
                  .annotate(port=Unnest('open_ports', distinct=True)) \
                  .values('port') \
                  .annotate(count=Count('port')) \
                  .order_by('-count', '-port')

    @classmethod
    def with_open_ports(cls, ports):
        """Return Port and how many IPs have it open"""
        return cls.objects.filter(open_ports__contains=ports)

    def __str__(self):
        # from django.contrib.postgres.aggregates import ArrayAgg
        # print(IP.objects.aggregate(arrayagg=ArrayAgg('inet')))
        # print(IP.objects.values('open_ports')\
        #       .annotate(number_of_days=Count('open_ports', distinct=True)))
        # print(IP.objects.filter()\
        #       .aggregate(Avg('open_ports')))
        # print(IP.objects.aggregate(All('open_ports')))
        # print(IP.stat())
        #       .group_by('inet'))
        # print(IP.objects.values('inet').annotate(arr_els=Unnest('open_ports')))
        # .values_list('arr_els', flat=True).distinct())
        return str(self.inet)
示例#8
0
class Nic(models.Model):
    model = models.CharField(max_length=30, blank=True)
    mac = MACAddressField(unique=True)
    integrated = models.BooleanField(default=False)
    management = models.BooleanField(default=False)
    host = models.ForeignKey(Host, on_delete=models.CASCADE, related_name='nics', null=True, blank=True)
    ip = InetAddressField(store_prefix_length=False, blank=True, null=True)
    primary = models.BooleanField(help_text="Is this the primary NIC in the assigned host?")
    lastseen = models.DateTimeField(null=True, blank=True, help_text="Updated automatically from primary LabScan")
    objects = NetManager()

    def __str__(self):
        return str(self.mac) + ' / ' + self.model

    def clean(self):
        if self.host:
            primaries = Nic.objects.filter(Q(primary=True) & Q(host=self.host) & ~Q(pk=self.pk))
            if primaries.count() > 1:
                raise ValidationError(f"The assigned host cannot have more than one primary NIC, id(s) {primaries.all()}")

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

        # update the IP fields on the card's parent host if it has one.
        if self.host!=None and self.ip:
            if self.primary:
                self.host.ip = self.ip
            elif self.host.ip == None:
                primarynic = self.host.nics.filter(primary=True)
                if primarynic:
                    if primarynic[0].ip is None:
                        self.host.ip = self.ip
            if self.host.lastseen:
                if self.lastseen > self.host.lastseen:
                    self.host.lastseen = self.lastseen
            else:
                self.host.lastseen = self.lastseen
            self.host.save()

        return super().save(*args, **kwargs)
示例#9
0
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', )
示例#10
0
class LoginAttempt(models.Model):
    '''
    A login attempt record (both successful and not).

    If user field is set then login was successful.

    Instead login and password fields are set.
    '''
    # https://docs.python.org/3/library/ipaddress.html
    # inet = InetAddressField(primary_key=True)
    ip = InetAddressField()
    login = models.CharField(
        max_length=260,
        null=True,
        blank=True,
    )
    password = models.CharField(
        max_length=260,
        null=True,
        blank=True,
    )
    user = models.ForeignKey(
        'core.User',
        default=None,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )
    time = models.DateTimeField(
        auto_now_add=True,
        db_index=True,
        null=True,
        blank=True,
    )
    # success = models.BooleanField(default=False)

    objects = NetManager()
示例#11
0
class Record(models.Model):
    """DMARC report record"""

    report = models.ForeignKey(Report, related_name='records', on_delete=models.CASCADE)
    source_ip = InetAddressField(store_prefix_length=False)
    recordcount = models.IntegerField()
    policyevaluated_disposition = models.CharField(max_length=10)
    policyevaluated_dkim = models.CharField(max_length=4)
    policyevaluated_spf = models.CharField(max_length=4)
    policyevaluated_reasontype = models.CharField(blank=True, max_length=75)
    policyevaluated_reasoncomment = models.CharField(blank=True, max_length=100)
    identifier_headerfrom = models.CharField(max_length=100)
    objects = NetManager()

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

    class Meta:
        indexes = (
            GistIndex(
                fields=('source_ip',), opclasses=('inet_ops',),
                name='dmarc_record_source_ip_idx'
            ),
        )
示例#12
0
class UniqueInetTestModel(Model):
    field = InetAddressField(unique=True)
    objects = NetManager()

    class Meta:
        db_table = 'uniqueinet'
示例#13
0
class InetTestModel(Model):
    field = InetAddressField()
    objects = NetManager()

    class Meta:
        db_table = 'inet'
示例#14
0
class BGPSession(ChangeLoggedModel, TaggableModel, PolicyMixin):
    """
    Abstract class used to define common caracteristics of BGP sessions.

    A BGP session is always defined with the following fields:
      * an autonomous system, it can also be called a peer
      * an IP address used to establish the session
      * a plain text password
      * an encrypted version of the password if the user asked for encryption
      * a TTL for multihoping
      * an enabled or disabled status telling if the session should be
        administratively up or down
      * import routing policies to apply to prefixes sent by the remote device
      * export routing policies to apply to prefixed sent to the remote device
      * a BGP state giving the current operational state of session (it will
        remain to unkown if the is disabled)
      * a received prefix count (it will stay none if polling is disabled)
      * a advertised prefix count (it will stay none if polling is disabled)
      * a date and time record of the last established state of the session
      * comments that consist of plain text that can use the markdown format
    """

    autonomous_system = models.ForeignKey("AutonomousSystem", on_delete=models.CASCADE)
    ip_address = InetAddressField(store_prefix_length=False, verbose_name="IP address")
    password = models.CharField(max_length=255, blank=True, null=True)
    encrypted_password = models.CharField(max_length=255, blank=True, null=True)
    multihop_ttl = TTLField(
        blank=True,
        default=1,
        verbose_name="Multihop TTL",
        help_text="Use a value greater than 1 for BGP multihop sessions",
    )
    enabled = models.BooleanField(default=True)
    import_routing_policies = models.ManyToManyField(
        "RoutingPolicy", blank=True, related_name="%(class)s_import_routing_policies"
    )
    export_routing_policies = models.ManyToManyField(
        "RoutingPolicy", blank=True, related_name="%(class)s_export_routing_policies"
    )
    bgp_state = models.CharField(
        max_length=50, choices=BGPState.choices, blank=True, null=True
    )
    service_reference = models.CharField(
        max_length=255,
        unique=True,
        blank=True,
        help_text="Optional internal service reference (auto-generated if left blank)",
    )
    received_prefix_count = models.PositiveIntegerField(blank=True, default=0)
    advertised_prefix_count = models.PositiveIntegerField(blank=True, default=0)
    last_established_state = models.DateTimeField(blank=True, null=True)
    comments = models.TextField(blank=True)

    objects = NetManager()
    logger = logging.getLogger("peering.manager.peering")

    class Meta:
        abstract = True
        ordering = ["autonomous_system", "ip_address"]

    def __str__(self):
        return self.service_reference

    @property
    def ip_address_version(self):
        return ipaddress.ip_address(self.ip_address).version

    def export_policies(self):
        return self.export_routing_policies.all()

    def import_policies(self):
        return self.import_routing_policies.all()

    def poll(self):
        raise NotImplementedError

    def get_bgp_state_html(self):
        """
        Return an HTML element based on the BGP state.
        """
        if self.bgp_state == BGPState.IDLE:
            badge = "danger"
        elif self.bgp_state in [BGPState.CONNECT, BGPState.ACTIVE]:
            badge = "warning"
        elif self.bgp_state in [BGPState.OPENSENT, BGPState.OPENCONFIRM]:
            badge = "info"
        elif self.bgp_state == BGPState.ESTABLISHED:
            badge = "success"
        else:
            badge = "secondary"

        text = '<span class="badge badge-{}">{}</span>'.format(
            badge, self.get_bgp_state_display() or "Unknown"
        )

        return mark_safe(text)

    def encrypt_password(self, commit=True):
        """
        Sets the `encrypted_password` field if a crypto module is found for the given
        platform. The field will be set to `None` otherwise.

        Returns `True` if the encrypted password has been changed, `False` otherwise.
        """
        try:
            router = getattr(self, "router")
        except AttributeError:
            router = getattr(self.ixp_connection, "router", None)

        if not router or not router.platform or not router.encrypt_passwords:
            return False

        if not self.password and self.encrypted_password:
            self.encrypted_password = ""
            if commit:
                self.save()
            return True

        if not self.encrypted_password:
            # If the password is not encrypted yet, do it
            self.encrypted_password = router.platform.encrypt_password(self.password)
        else:
            # Try to re-encrypt the encrypted password, if the resulting string is the
            # same it means the password matches the router platform algorithm
            is_up_to_date = self.encrypted_password == router.platform.encrypt_password(
                self.encrypted_password
            )
            if not is_up_to_date:
                self.encrypted_password = router.platform.encrypt_password(
                    self.password
                )

        # Check if the encrypted password matches the clear one
        # Force re-encryption if there a difference
        if self.password != router.platform.decrypt_password(self.encrypted_password):
            self.encrypted_password = router.platform.encrypt_password(self.password)

        if commit:
            self.save()
        return True

    def generate_service_reference(self):
        """
        Generate a unique service reference for a session from local ASN with 6 digit
        hex UUID.

        Example: IX9268-FD130FS/IX<asn>-<hex>S
        Example: D9268-4CD335S/D<asn>-<hex>S

        """

        asn, prefix = "", ""

        # Find out ASN and prefix for the service ID based on the type of session
        if hasattr(self, "ixp_connection"):
            asn = str(
                self.ixp_connection.internet_exchange_point.local_autonomous_system.asn
            )
            prefix = "IX"
        else:
            asn = str(self.local_autonomous_system.asn)
            prefix = "D"

        return f"{prefix}{asn}-{uuid.uuid4().hex[:6].upper()}S"

    def save(self, *args, **kwargs):
        """
        Overrides default `save()` to set the service reference if left blank.
        """
        if not self.service_reference:
            self.service_reference = self.generate_service_reference()

        return super().save(*args, **kwargs)
示例#15
0
class NullInetTestModel(Model):
    field = InetAddressField(null=True)
    objects = NetManager()

    class Meta:
        db_table = 'nullinet'
示例#16
0
class IPRange(AbstractDatedModel):
    subnet = models.ForeignKey('ipam.Subnet')
    range_begin = InetAddressField(store_prefix_length=False)
    range_end = InetAddressField(store_prefix_length=False)

    version = models.PositiveSmallIntegerField(choices=AV_CHOICES, editable=False)

    role = models.ForeignKey('ipam.IPRangeRole', blank=True, null=True)

    description = models.CharField(max_length=200, blank=True)
    notes = models.TextField(blank=True)

    def clean(self):
        field_errors = {}

        # range is reversed
        if self.range_begin > self.range_end:
            raise ValidationError(
                _("Proposed range is reversed"),
                code='reversed_range'
            )

        netaddr_cidr = IPNetwork(str(self.subnet.cidr))

        if int(self.range_begin) < netaddr_cidr.first:
            field_errors.update({
                'range_begin':
                    ValidationError(_(
                        '%(begin)s is smaller than %(subnet)s first possible address (%(address)s)'
                    ),
                        params={
                            'begin': self.range_begin,
                            'subnet': netaddr_cidr,
                            'address': netaddr_cidr.network
                        },
                        code='invalid_begin_value'
                    )
            })

        if int(self.range_end) > netaddr_cidr.last:
            field_errors.update({
                'range_end':
                    ValidationError(_(
                        '%(end)s is greater than %(subnet)s last possible address (%(address)s)'
                    ),
                        params={
                            'end': self.range_end,
                            'subnet': netaddr_cidr,
                            'address': netaddr_cidr.broadcast
                        },
                        code='invalid_end_value'
                    )
            })

        if field_errors:
            raise ValidationError(field_errors)
        subnets_in_range = self.subnets_in_range.filter(supernet=self.subnet)
        if subnets_in_range.exists():
            raise ValidationError(
                _("Range %(range)s clashes with subnet(s) %(subnet)s"),
                code="conflict_with_subnet",
                params={
                    'range': self.range,
                    'subnet': ", ".join((str(s) for s in subnets_in_range))
                }
            )

    @property
    def subnets_in_range(self):
        from ipam.models import Subnet
        contains_or_eq_begin = Q(cidr__net_contains_or_equals=self.range_begin)
        contains_or_eq_end = Q(cidr__net_contains_or_equals=self.range_end)
        in_range = Q(cidr__range=(self.range.first, self.range.last))
        return Subnet.objects.filter(
            Q(contains_or_eq_begin | contains_or_eq_end | in_range)
        )

    @property
    def range(self):
        return netaddr.IPRange(str(self.range_begin), str(self.range_end))

    @range.setter
    def range(self, value):
        self.range_begin = value[0]
        self.range_end = value[-1]

    def save(self, *args, **kwargs):
        self.version = self.range.version

        if self.range_begin > self.range_end:
            raise ValueError("Proposed range is reversed")

        netaddr_cidr = IPNetwork(str(self.subnet.cidr))

        if int(self.range_begin) < netaddr_cidr.first:
            raise ValueError(
                '%(begin)s is smaller than %(subnet)s first possible address (%(address)s)' % {
                    'begin': self.range_begin,
                    'subnet': netaddr_cidr,
                    'address': netaddr_cidr.network
                }
            )

        if int(self.range_end) > netaddr_cidr.last:
            raise ValueError(
                '%(end)s is greater than %(subnet)s last possible address (%(address)s)' % {
                    'end': self.range_end,
                    'subnet': netaddr_cidr,
                    'address': netaddr_cidr.broadcast
                }
            )

        subnets_in_range = self.subnets_in_range.filter(supernet=self.subnet)
        if subnets_in_range.exists():
            raise ValueError(
                "Range %(range)s clashes with subnet(s) %(subnet)s" % {
                    'range': self.range,
                    'subnet': ", ".join((str(s) for s in subnets_in_range))
                }
            )

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

    def __str__(self):
        return str(self.range)
示例#17
0
class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ("groupapp", "0001_initial"),
    ]

    operations = [
        migrations.CreateModel(
            name="NetworkModel",
            fields=[
                ("id",
                 models.AutoField(auto_created=True,
                                  primary_key=True,
                                  serialize=False,
                                  verbose_name="ID")),
                (
                    "network",
                    InetAddressField(
                        help_text=
                        "Ip address of network. For example: 192.168.1.0 or fde8:6789:1234:1::",
                        unique=True,
                        verbose_name="IP network",
                    ),
                ),
                (
                    "kind",
                    models.PositiveSmallIntegerField(
                        choices=[
                            (0, "Not defined"),
                            (1, "Internet"),
                            (2, "Guest"),
                            (3, "Trusted"),
                            (4, "Devices"),
                            (5, "Admin"),
                        ],
                        default=0,
                        verbose_name="Kind of network",
                    ),
                ),
                ("description",
                 models.CharField(max_length=64, verbose_name="Description")),
                ("ip_start",
                 models.GenericIPAddressField(
                     verbose_name="Start work ip range")),
                ("ip_end",
                 models.GenericIPAddressField(
                     verbose_name="End work ip range")),
                ("groups",
                 models.ManyToManyField(to="groupapp.Group",
                                        verbose_name="Groups")),
            ],
            options={
                "verbose_name": "Network",
                "verbose_name_plural": "Networks",
                "db_table": "networks_network",
                "ordering": ("network", ),
            },
        ),
    ]
class BGPSession(ChangeLoggedModel, TaggableModel, PolicyMixin):
    """
    Abstract class used to define common caracteristics of BGP sessions.

    A BGP session is always defined with the following fields:
      * a unique service reference, blank or user defined
      * an autonomous system, it can also be called a peer
      * an IP address used to establish the session
      * a plain text password
      * an encrypted version of the password if the user asked for encryption
      * a TTL for multihoping
      * an enabled or disabled status telling if the session should be
        administratively up or down
      * import routing policies to apply to prefixes sent by the remote device
      * export routing policies to apply to prefixed sent to the remote device
      * a BGP state giving the current operational state of session (it will
        remain to unkown if the is disabled)
      * a received prefix count (it will stay none if polling is disabled)
      * a advertised prefix count (it will stay none if polling is disabled)
      * a date and time record of the last established state of the session
      * comments that consist of plain text that can use the markdown format
    """

    service_reference = models.CharField(
        max_length=255,
        unique=True,
        blank=True,
        null=True,
        help_text="Optional internal service reference",
    )
    autonomous_system = models.ForeignKey(
        to="peering.AutonomousSystem", on_delete=models.CASCADE
    )
    ip_address = InetAddressField(store_prefix_length=False, verbose_name="IP address")
    password = models.CharField(max_length=255, blank=True, null=True)
    encrypted_password = models.CharField(max_length=255, blank=True, null=True)
    multihop_ttl = TTLField(
        blank=True,
        default=1,
        verbose_name="Multihop TTL",
        help_text="Use a value greater than 1 for BGP multihop sessions",
    )
    enabled = models.BooleanField(default=True)
    import_routing_policies = models.ManyToManyField(
        to="peering.RoutingPolicy",
        blank=True,
        related_name="%(class)s_import_routing_policies",
    )
    export_routing_policies = models.ManyToManyField(
        to="peering.RoutingPolicy",
        blank=True,
        related_name="%(class)s_export_routing_policies",
    )
    bgp_state = models.CharField(
        max_length=50, choices=BGPState.choices, blank=True, null=True
    )
    received_prefix_count = models.PositiveIntegerField(blank=True, default=0)
    advertised_prefix_count = models.PositiveIntegerField(blank=True, default=0)
    last_established_state = models.DateTimeField(blank=True, null=True)
    comments = models.TextField(blank=True)

    objects = NetManager()
    logger = logging.getLogger("peering.manager.peering")

    class Meta:
        abstract = True
        ordering = ["service_reference", "autonomous_system", "ip_address"]

    def __str__(self):
        return (
            self.service_reference
            or f"AS{self.autonomous_system.asn} - {self.ip_address}"
        )

    def _merge_policies(self, merged_policies, new_policies):
        if type(self.ip_address) in (int, str):
            ip_address = ipaddress.ip_address(self.ip_address)
        else:
            ip_address = self.ip_address

        for policy in new_policies:
            # Only merge universal policies or policies of same IP family
            if policy in merged_policies or policy.address_family not in (
                IPFamily.ALL,
                ip_address.version,
            ):
                continue
            merged_policies.append(policy)
        return merged_policies

    def export_policies(self):
        return self.export_routing_policies.all()

    def merged_export_policies(self, reverse=False):
        merged = [p for p in self.export_policies()]

        # Merge policies from nested objects (first AS, then BGP group)
        self._merge_policies(merged, self.autonomous_system.export_policies())

        group = None
        if hasattr(self, "ixp_connection"):
            group = self.ixp_connection.internet_exchange_point
        else:
            group = self.bgp_group

        if group:
            self._merge_policies(merged, group.export_policies())

        return list(reversed(merged)) if reverse else merged

    def import_policies(self):
        return self.import_routing_policies.all()

    def merged_import_policies(self, reverse=False):
        # Get own policies
        merged = [p for p in self.import_policies()]

        # Merge policies from nested objects (first AS, then BGP group)
        self._merge_policies(merged, self.autonomous_system.import_policies())

        group = None
        if hasattr(self, "ixp_connection"):
            group = self.ixp_connection.internet_exchange_point
        else:
            group = self.bgp_group

        if group:
            self._merge_policies(merged, group.import_policies())

        return list(reversed(merged)) if reverse else merged

    def merged_communities(self):
        merged = [c for c in self.autonomous_system.communities.all()]

        group = None
        if hasattr(self, "ixp_connection"):
            group = self.ixp_connection.internet_exchange_point
        else:
            group = self.bgp_group

        for c in group.communities.all():
            if c not in merged:
                merged.append(c)

        return merged

    def poll(self):
        raise NotImplementedError

    def get_bgp_state_html(self):
        """
        Return an HTML element based on the BGP state.
        """
        if self.bgp_state == BGPState.IDLE:
            badge = "danger"
        elif self.bgp_state in (BGPState.CONNECT, BGPState.ACTIVE):
            badge = "warning"
        elif self.bgp_state in (BGPState.OPENSENT, BGPState.OPENCONFIRM):
            badge = "info"
        elif self.bgp_state == BGPState.ESTABLISHED:
            badge = "success"
        else:
            badge = "secondary"

        return mark_safe(
            f'<span class="badge badge-{badge}">{self.get_bgp_state_display() or "Unknown"}</span>'
        )

    def encrypt_password(self, commit=True):
        """
        Sets the `encrypted_password` field if a crypto module is found for the given
        platform. The field will be set to `None` otherwise.

        Returns `True` if the encrypted password has been changed, `False` otherwise.
        """
        try:
            router = getattr(self, "router")
        except AttributeError:
            router = getattr(self.ixp_connection, "router", None)

        if not router or not router.platform or not router.encrypt_passwords:
            return False

        if not self.password and self.encrypted_password:
            self.encrypted_password = ""
            if commit:
                self.save()
            return True

        if not self.encrypted_password:
            # If the password is not encrypted yet, do it
            self.encrypted_password = router.platform.encrypt_password(self.password)
        else:
            # Try to re-encrypt the encrypted password, if the resulting string is the
            # same it means the password matches the router platform algorithm
            is_up_to_date = self.encrypted_password == router.platform.encrypt_password(
                self.encrypted_password
            )
            if not is_up_to_date:
                self.encrypted_password = router.platform.encrypt_password(
                    self.password
                )

        # Check if the encrypted password matches the clear one
        # Force re-encryption if there a difference
        if self.password != router.platform.decrypt_password(self.encrypted_password):
            self.encrypted_password = router.platform.encrypt_password(self.password)

        if commit:
            self.save()
        return True
示例#19
0
class NetworkIXLan(models.Model):
    asn = ASNField(verbose_name="ASN")
    ipaddr4 = InetAddressField(
        verbose_name="IPv4",
        validators=[AddressFamilyValidator(4)],
        blank=True,
        null=True,
    )
    ipaddr6 = InetAddressField(
        verbose_name="IPv6",
        validators=[AddressFamilyValidator(6)],
        blank=True,
        null=True,
    )
    is_rs_peer = models.BooleanField(default=False)
    notes = models.CharField(max_length=255, blank=True)
    speed = models.PositiveIntegerField()
    operational = models.BooleanField(default=True)
    net = models.ForeignKey(
        to="peeringdb.Network",
        default=0,
        related_name="netixlan_set",
        verbose_name="Network",
        on_delete=models.CASCADE,
    )
    ixlan = models.ForeignKey(
        to="peeringdb.IXLan",
        default=0,
        related_name="netixlan_set",
        verbose_name="Internet Exchange LAN",
        on_delete=models.CASCADE,
    )

    ignored_fields = ["ix_id", "name"]

    class Meta:
        verbose_name = "Public Peering Exchange Point"
        verbose_name_plural = "Public Peering Exchange Points"

    @property
    def cidr4(self):
        try:
            return self.cidr(address_family=4)
        except ValueError:
            return None

    @property
    def cidr6(self):
        try:
            return self.cidr(address_family=6)
        except ValueError:
            return None

    def get_ixlan_prefix(self, address_family=0):
        """
        Returns matching `CidrAddressField` containing this `NetworkIXLan`'s IP
        addresses. When `address_family` is set to `4` or `6` only the prefix also
        matching the address family will be returned.
        """
        prefixes = IXLanPrefix.objects.filter(ixlan=self.ixlan)
        if address_family in (4, 6):
            prefixes = prefixes.filter(prefix__family=address_family)

        r = []
        if address_family != 6:
            for p in prefixes:
                if self.ipaddr4 in p.prefix:
                    r.append(p.prefix)
                    break
        if address_family != 4:
            for p in prefixes:
                if self.ipaddr6 in p.prefix:
                    r.append(p.prefix)
                    break

        return r if len(r) != 1 else r[0]

    def cidr(self, address_family=4):
        """
        Returns a Python IP interface object with the IP address and prefix length
        set.
        """
        if address_family not in (4, 6):
            raise ValueError("Address family must be 4 or 6")
        if address_family == 4 and not self.ipaddr4:
            raise ValueError("IPv4 address is not set")
        if address_family == 6 and not self.ipaddr6:
            raise ValueError("IPv6 address is not set")

        prefix = self.get_ixlan_prefix(address_family=address_family)
        address = self.ipaddr4 if address_family == 4 else self.ipaddr6

        return ipaddress.ip_interface(f"{address.ip}/{prefix.prefixlen}")
示例#20
0
class IPAddress(AbstractDatedModel):
    """
    An IPAddress represents an individual IPv4 or IPv6 address and its mask.

    An IPAddress can optionally be assigned to an Interface.
    Interfaces can have zero or more IPAddresses assigned to them.

    An IPAddress can also optionally point to a NAT inside IP, designating itself as a
    NAT outside IP. This is useful, for example, when mapping public addresses to private addresses.
    When an Interface has been assigned an IPAddress which has a NAT outside IP, that Interface's
    Device can use either the inside or outside IP as its primary IP.
    """

    subnet = models.ForeignKey('ipam.Subnet', editable=False)
    address = InetAddressField()
    version = models.PositiveSmallIntegerField(choices=AV_CHOICES,
                                               editable=False)

    interface = models.ForeignKey('infrastructure.Interface',
                                  blank=True,
                                  null=True)

    status = models.PositiveSmallIntegerField(
        'Status',
        choices=IPADDRESS_STATUS_CHOICES,
        default=IPADDRESS_STATUS_ACTIVE,
        help_text='The operational status of this IP')
    role = models.PositiveSmallIntegerField(
        'Role',
        choices=IPADDRESS_ROLE_CHOICES,
        blank=True,
        null=True,
        help_text='The functional role of this IP')

    nat_inside = models.OneToOneField(
        'self',
        related_name='nat_outside',
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        verbose_name='NAT (Inside)',
        help_text="The IP for which this address is the \"outside\" "
        "IP")

    description = models.CharField(max_length=200, blank=True)
    notes = models.TextField(blank=True)

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

        if not isinstance(self.address, netaddr.IPAddress):
            self.address = netaddr.IPAddress(self.address)

        self.version = self.address.version

        parent = self.find_parent()

        if parent:
            self.subnet = parent
        else:
            raise ValueError("No parent subnet found for address {}".format(
                self.address))

        if self.is_reserved_address:
            raise ValueError(
                "Address {} clashes with reserved addresses for subnet {}".
                format(self.address, self.subnet))

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

    def find_parent(self):
        from .subnet import Subnet
        return Subnet.objects.filter(
            cidr__net_contains=self.address).order_by('-cidr').first()

    @property
    def is_reserved_address(self):
        address_value = int(self.address)
        parent_cidr = self.subnet.cidr
        network_address_value = int(parent_cidr.network_address)
        broadcast_address_value = int(parent_cidr.broadcast_address)
        if address_value == network_address_value:
            return True
        if address_value == broadcast_address_value:
            return True
        return False

    class Meta:
        ordering = ['version', 'address']
        verbose_name = 'IP address'
        verbose_name_plural = 'IP addresses'

    @cached_property
    def vrf(self):
        return self.subnet.vrf

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

    def get_duplicates(self):
        return IPAddress.objects.filter(
            subnet=self.subnet, address=self.address).exclude(pk=self.pk)

    def clean(self):

        if self.address:
            # Find parent subnet
            self.subnet = self.find_parent()

            if self.subnet is None:
                raise ValidationError(
                    {'address': "No parent subnet found for this address"})

            # 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_ips = self.get_duplicates()
                if duplicate_ips:
                    raise ValidationError({
                        'address':
                        "Duplicate IP address found in {}: {}".format(
                            "VRF {}".format(self.vrf)
                            if self.vrf else "global table",
                            duplicate_ips.first(),
                        )
                    })

    @property
    def device(self):
        if self.interface:
            return self.interface.device
        return None

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

    def get_role_class(self):
        return ROLE_CHOICE_CSS[self.role]
示例#21
0
class NicScanConflict(ScanConflict):
    newip = InetAddressField(null=True)
    nic = models.ForeignKey(Nic, on_delete=models.CASCADE)

    def __str__(self):
        return f"[nic conflict] {self.nic}: {self.nic.ip} vs {self.newip}"
示例#22
0
class AggregateTestModel(Model):
    network = CidrAddressField(blank=True, null=True, default=None)
    inet = InetAddressField(blank=True, null=True, default=None)
示例#23
0
class Address(models.Model):
    address = InetAddressField(primary_key=True, store_prefix_length=False)
    # Force manual removal of addresses so they are unassigned and properly re-classified
    host = models.ForeignKey(
        "hosts.Host",
        db_column="mac",
        blank=True,
        null=True,
        related_name="addresses",
        on_delete=models.SET_NULL,
    )
    pool = models.ForeignKey(
        "Pool", db_column="pool", blank=True, null=True, on_delete=models.SET_NULL
    )
    reserved = models.BooleanField(default=False)
    # Do we want to allow deletion of a network with addresses referencing it?
    network = models.ForeignKey(
        "Network", db_column="network", related_name="net_addresses"
    )
    changed = models.DateTimeField(auto_now=True)
    changed_by = models.ForeignKey(settings.AUTH_USER_MODEL, db_column="changed_by")

    # objects = AddressQuerySet.as_manager()
    objects = AddressManager.from_queryset(AddressQuerySet)()

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

    @property
    def last_mac_seen(self):
        from openipam.hosts.models import GulRecentArpBymac

        gul_mac = (
            GulRecentArpBymac.objects.filter(mac=self.mac)
            .order_by("-stopstamp")
            .first()
        )
        return gul_mac[0].mac if gul_mac else None

    @property
    def last_seen(self):
        from openipam.hosts.models import GulRecentArpByaddress

        gul_ip = (
            GulRecentArpByaddress.objects.filter(address=self.address)
            .order_by("-stopstamp")
            .first()
        )
        return gul_ip.stopstamp if gul_ip else None

    def clean(self):
        if self.host and self.pool:
            raise ValidationError(
                "Host and Pool cannot both be defined.  Choose one or the other."
            )
        elif (self.host or self.pool) and self.reserved:
            raise ValidationError(
                "If a Host or Pool are defined, reserved must be false."
            )
        elif self.address not in self.network.network:
            raise ValidationError(
                "Address entered must be a part of the network selected."
            )

    def save(self, *args, **kwargs):
        self.full_clean()
        return super(Address, self).save(*args, **kwargs)

    class Meta:
        db_table = "addresses"
        verbose_name_plural = "addresses"
示例#24
0
文件: ixctl.py 项目: fullctl/ixctl
class InternetExchangeMember(PdbRefModel):
    """
    Describes a member at an internet exchange

    Can have a reference to a peeringdb netixlan object
    """

    ix = models.ForeignKey(
        InternetExchange,
        help_text=_("Members at this Exchange"),
        related_name="member_set",
        on_delete=models.CASCADE,
    )
    ipaddr4 = InetAddressField(blank=True,
                               null=True,
                               store_prefix_length=False)
    ipaddr6 = InetAddressField(blank=True,
                               null=True,
                               store_prefix_length=False)
    macaddr = MACAddressField(null=True, blank=True)
    as_macro_override = models.CharField(max_length=255,
                                         blank=True,
                                         null=True,
                                         validators=[validate_as_set])
    is_rs_peer = models.BooleanField(default=False)
    speed = models.PositiveIntegerField()
    asn = models.PositiveIntegerField()
    name = models.CharField(max_length=255, blank=True, null=True)

    ixf_state = models.CharField(max_length=255,
                                 default="active",
                                 choices=django_ixctl.enum.MEMBER_STATE)
    ixf_member_type = models.CharField(
        max_length=255,
        choices=django_ixctl.enum.IXF_MEMBER_TYPE,
        default="peering")

    class PdbRef(PdbRefModel.PdbRef):
        pdbctl = pdbctl.NetworkIXLan

    class HandleRef:
        tag = "member"

    class Meta:
        db_table = "ixctl_member"
        verbose_name_plural = _("Internet Exchange Members")
        verbose_name = _("Internet Exchange Member")
        unique_together = (("ipaddr4", "ix"), ("ipaddr6", "ix"), ("macaddr",
                                                                  "ix"))

    @classmethod
    def create_from_pdb(cls, pdb_object, ix, save=True, **fields):
        """
        Create `InternetExchangeMember` from peeringdb netixlan

        Argument(s):

        - pdb_object (`fullctl.service_bridge.pdbctl.NetworkIXLan`): netixlan instance
        - ix (`InternetExchange`): member of this ix

        Keyword Argument(s):

        And keyword arguments passwed will be used to inform properties
        of the InternetExchangeMember to be created
        """

        member = super().create_from_pdb(pdb_object,
                                         ix=ix,
                                         save=False,
                                         **fields)

        member.ipaddr4 = pdb_object.ipaddr4
        member.ipaddr6 = pdb_object.ipaddr6
        member.is_rs_peer = pdb_object.is_rs_peer
        member.speed = pdb_object.speed
        member.asn = pdb_object.net.asn
        member.name = pdb_object.net.name

        if save:
            member.save()

        return member

    @classmethod
    def preload_as_macro(cls, queryset):
        asns = set([member.asn for member in queryset])
        if not asns:
            return queryset
        asn_map = {}
        for net in sot.ASSet().objects(asns=list(asns)):
            asn_map[net.asn] = net
        for member in queryset:
            member._net = asn_map.get(member.asn)
            yield member

    @property
    def display_name(self):
        return self.name or f"AS{self.asn}"

    @property
    def org(self):
        return self.ix.instance.org

    @property
    def ix_name(self):
        return self.ix.name

    @property
    def as_sets(self):
        if not self.as_macro:
            return []

        return [as_set.strip() for as_set in self.as_macro.split(",")]

    @property
    def as_macro(self):
        if self.as_macro_override:
            return self.as_macro_override

        if self.net:
            if self.net.source == "peerctl":
                return self.net.as_set
            elif self.net.source == "pdbctl":
                return self.net.irr_as_set
        return ""

    @property
    def net(self):
        if hasattr(self, "_net"):
            return self._net

        self._net = sot.ASSet().first(asn=self.asn)
        return self._net

    def __str__(self):
        return f"AS{self.asn} - {self.ipaddr4} - {self.ipaddr6} ({self.id})"
示例#25
0
class NetworkIXLAN(models.Model):
    asn = ASNField()
    name = models.CharField(max_length=255)
    ipaddr6 = InetAddressField(
        store_prefix_length=False,
        blank=True,
        null=True,
        validators=[AddressFamilyValidator(6)],
    )
    ipaddr4 = InetAddressField(
        store_prefix_length=False,
        blank=True,
        null=True,
        validators=[AddressFamilyValidator(4)],
    )
    is_rs_peer = models.BooleanField(default=False)
    ix_id = models.PositiveIntegerField()
    ixlan_id = models.PositiveIntegerField()

    objects = NetManager()
    logger = logging.getLogger("peering.manager.peeringdb")

    class Meta:
        ordering = ["asn", "ipaddr6", "ipaddr4"]
        verbose_name = "Network IX LAN"
        verbose_name_plural = "Network IX LANs"

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

        # Ignore if we do not have any IP addresses
        if not self.ipaddr6 and not self.ipaddr4:
            self.logger.debug(
                "network ixlan with as%s and ixlan id %s ignored"
                ", no ipv6 and no ipv4",
                self.asn,
                self.ixlan_id,
            )
            return

        # Trigger the build of a new PeerRecord or just ignore if it already
        # exists, assumes it exists
        # Note that there is not point of doing the same thing when a Network
        # or a NetworkIXLAN is deleted because it will automatically delete the
        # PeerRecord linked to it using the foreign key (with the CASCADE mode)
        network = None
        peer_record_exists = True

        try:
            # Try guessing the Network given its ASN
            network = Network.objects.get(asn=self.asn)
            # Try finding if the PeerRecord already exists
            PeerRecord.objects.get(network=network, network_ixlan=self)
        except Network.DoesNotExist:
            # The network does not exist, well not much to do
            self.logger.debug(
                "network with as%s does not exist, required for "
                "peer record creation",
                self.asn,
            )
        except PeerRecord.DoesNotExist:
            # But if the exception is raised, it does not
            peer_record_exists = False

        # If the PeerRecord does not exist, create it
        if not peer_record_exists:
            PeerRecord.objects.create(network=network, network_ixlan=self)
            self.logger.debug(
                "peer record created with as%s and ixlan id %s", self.asn, self.ixlan_id
            )
        else:
            self.logger.debug(
                "peer record with as%s and ixlan id %s exists", self.asn, self.ixlan_id
            )

    def __str__(self):
        return "AS{} on {} - IPv6: {} - IPv4: {}".format(
            self.asn, self.name, self.ipaddr6, self.ipaddr4
        )
示例#26
0
class InetArrayTestModel(Model):
    field = ArrayField(InetAddressField(), blank=True, null=True)

    class Meta:
        db_table = 'inetarray'
示例#27
0
class NoPrefixInetTestModel(Model):
    field = InetAddressField(store_prefix_length=False)
    objects = NetManager()

    class Meta:
        db_table = 'noprefixinet'
示例#28
0
class Connection(ChangeLoggedModel, TaggableModel):
    peeringdb_netixlan = models.ForeignKey("peeringdb.NetworkIXLan",
                                           on_delete=models.SET_NULL,
                                           blank=True,
                                           null=True)
    state = models.CharField(max_length=20,
                             choices=ConnectionState.choices,
                             default=ConnectionState.ENABLED)
    vlan = VLANField(verbose_name="VLAN", blank=True, null=True)
    ipv6_address = InetAddressField(
        store_prefix_length=False,
        blank=True,
        null=True,
        validators=[AddressFamilyValidator(6)],
    )
    ipv4_address = InetAddressField(
        store_prefix_length=False,
        blank=True,
        null=True,
        validators=[AddressFamilyValidator(4)],
    )
    internet_exchange_point = models.ForeignKey("peering.InternetExchange",
                                                blank=True,
                                                null=True,
                                                on_delete=models.SET_NULL)
    router = models.ForeignKey("peering.Router",
                               blank=True,
                               null=True,
                               on_delete=models.SET_NULL)
    interface = models.CharField(max_length=200, blank=True)
    description = models.CharField(max_length=200, blank=True)
    comments = models.TextField(blank=True)

    objects = NetManager()

    @property
    def name(self):
        return str(self)

    @property
    def linked_to_peeringdb(self):
        """
        Tells if the PeeringDB object for this connection still exists.
        """
        return self.peeringdb_netixlan is not None

    def __str__(self):
        s = ""

        if self.internet_exchange_point:
            s += str(self.internet_exchange_point)

        if self.router:
            if s:
                s += " on "
            s += str(self.router)

            if self.interface:
                s += f" {self.interface}"

        return s or f"Connection #{self.pk}"

    def get_absolute_url(self):
        return reverse("net:connection_details", kwargs={"pk": self.pk})

    def link_to_peeringdb(self):
        """
        Retrieves the PeeringDB ID for this IX connection based on the IP addresses
        that have been recorded. The PeeringDB record will be returned on success. In
        any other cases `None` will be returned. The value will also be saved in the
        corresponding field of the model.
        """
        try:
            netixlan = NetworkIXLan.objects.get(ipaddr6=self.ipv6_address,
                                                ipaddr4=self.ipv4_address)
        except NetworkIXLan.DoesNotExist:
            return None

        self.peeringdb_netixlan = netixlan
        self.save()

        return netixlan
示例#29
0
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', )
示例#30
0
文件: ixctl.py 项目: fullctl/ixctl
class Routeserver(HandleRefModel):
    """
    Describes a routeserver at an internet exchange
    """

    ix = models.ForeignKey(
        InternetExchange,
        on_delete=models.CASCADE,
        related_name="rs_set",
    )

    # RS Config

    name = models.CharField(
        max_length=255,
        help_text=_("Routeserver name"),
    )

    asn = ASNField(help_text=_("ASN"))

    router_id = InetAddressField(
        store_prefix_length=False,
        help_text=_("Router Id"),
    )

    rpki_bgp_origin_validation = models.BooleanField(default=False)

    # ARS Config

    ars_type = models.CharField(
        max_length=32,
        choices=django_ixctl.enum.ARS_TYPES,
        default="bird",
    )

    max_as_path_length = models.IntegerField(
        default=32,
        help_text=_("Max length of AS_PATH attribute."),
    )

    no_export_action = models.CharField(
        max_length=8,
        choices=django_ixctl.enum.ARS_NO_EXPORT_ACTIONS,
        default="pass",
        help_text=_(
            "RFC1997 well-known communities (NO_EXPORT and NO_ADVERTISE)"),
    )

    graceful_shutdown = models.BooleanField(
        default=False,
        help_text=_("Graceful BGP session shutdown"),
    )

    extra_config = models.TextField(null=True,
                                    blank=True,
                                    help_text=_("Extra arouteserver config"))

    class Meta:
        db_table = "ixctl_rs"
        unique_together = (("ix", "router_id"), )

    class HandleRef:
        tag = "rs"

    @property
    def org(self):
        return self.ix.instance.org

    @property
    def display_name(self):
        return self.name

    @property
    def rsconf(self):
        """
        Return the rsconf instance for this routeserver

        Will create the rsconf instance if it does not exist yet
        """
        if not hasattr(self, "_rsconf"):
            rsconf, created = RouteserverConfig.objects.get_or_create(rs=self)
            self._rsconf = rsconf
        return self._rsconf

    @property
    def rsconf_status_dict(self):
        """
        Returns a status dict for the current state of this routeserver's
        configuration
        """

        rsconf = self.rsconf

        task = rsconf.task

        # no status

        if not task and not rsconf.rs_response:
            return {"status": None}

        if not task:
            return rsconf.rs_response

        if task.status == "pending":
            return {"status": "queued"}
        if task.status == "running":
            return {"status": "generating"}
        if task.status == "cancelled":
            return {"status": "canceled"}
        if task.status == "failed":
            return {"status": "error", "error": task.error}
        if task.status == "completed":
            if not rsconf.rs_response:
                return {"status": "generated"}
            return rsconf.rs_response

        return {"status": None}

    @property
    def rsconf_status(self):
        return self.rsconf_status_dict.get("status")

    @property
    def rsconf_response(self):
        return self.rsconf.rs_response

    @property
    def rsconf_error(self):
        return self.rsconf_status_dict.get("error")

    @property
    def ars_general(self):
        """
        Generate and return `dict` for ARouteserver general config
        """

        ars_general = {
            "cfg": {
                "rs_as": self.asn,
                "router_id": f"{self.router_id}",
                "filtering": {
                    "max_as_path_len": self.max_as_path_length,
                    "rpki_bgp_origin_validation": {
                        "enabled": self.rpki_bgp_origin_validation
                    },
                },
                "rfc1997_wellknown_communities": {
                    "policy": self.no_export_action,
                },
                "graceful_shutdown": {
                    "enabled": self.graceful_shutdown
                },
            }
        }

        if self.extra_config:
            extra_config = yaml.load(self.extra_config, Loader=Loader)

            # TODO: should we expect people to put the cfg:
            # root element into the extra config or not ?
            #
            # support both approaches for now

            if "cfg" in extra_config:
                ars_general["cfg"].update(extra_config["cfg"])
            else:
                ars_general.update(extra_config)

        return ars_general

    @property
    def ars_clients(self):
        """
        Generate and return `dirct` for ARouteserver clients config
        """

        asns = []
        asn_as_sets = {}
        clients = {}

        # TODO
        # where to get ASN sets from ??
        # peeringdb network ??
        rs_peers = InternetExchangeMember.preload_as_macro(
            self.ix.member_set.filter(is_rs_peer=True))

        for member in rs_peers:
            if member.asn not in asns:
                asns.append(member.asn)
            if member.asn not in clients:
                clients[member.asn] = {"asn": member.asn, "ip": [], "cfg": {}}

            if member.ipaddr4:
                clients[member.asn]["ip"].append(f"{member.ipaddr4}")

            if member.ipaddr6:
                clients[member.asn]["ip"].append(f"{member.ipaddr6}")

            if member.as_macro:
                clients[member.asn]["cfg"].update(
                    filtering={"irrdb": {
                        "as_sets": member.as_sets,
                    }})

        if asns:
            for net in pdbctl.Network().objects(asns=asns):
                as_set = get_as_set(net)
                if as_set:
                    asn_as_sets[f"AS{net.asn}"] = {"as_sets": [as_set]}

        print(asn_as_sets)

        return {"asns": asn_as_sets, "clients": list(clients.values())}

    def __str__(self):
        return f"Routeserver {self.name} AS{self.asn}"