class AggregateTestChildModel(Model): parent = ForeignKey( 'AggregateTestModel', related_name='children', on_delete=CASCADE, ) network = CidrAddressField() inet = InetAddressField()
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"
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}"
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",)
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)
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}"
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)
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)
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 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()
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' ), )
class UniqueInetTestModel(Model): field = InetAddressField(unique=True) objects = NetManager() class Meta: db_table = 'uniqueinet'
class InetTestModel(Model): field = InetAddressField() objects = NetManager() class Meta: db_table = 'inet'
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)
class NullInetTestModel(Model): field = InetAddressField(null=True) objects = NetManager() class Meta: db_table = 'nullinet'
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)
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
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}")
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]
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}"
class AggregateTestModel(Model): network = CidrAddressField(blank=True, null=True, default=None) inet = InetAddressField(blank=True, null=True, default=None)
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"
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})"
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 )
class InetArrayTestModel(Model): field = ArrayField(InetAddressField(), blank=True, null=True) class Meta: db_table = 'inetarray'
class NoPrefixInetTestModel(Model): field = InetAddressField(store_prefix_length=False) objects = NetManager() class Meta: db_table = 'noprefixinet'
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
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 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}"