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 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 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
Exemple #4
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}")
Exemple #5
0
class InternetExchange(ChangeLoggedModel, TemplateModel):
    peeringdb_id = models.PositiveIntegerField(blank=True, default=0)
    name = models.CharField(max_length=128)
    slug = models.SlugField(unique=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)],
    )
    comment = models.TextField(blank=True)
    configuration_template = models.ForeignKey(
        "ConfigurationTemplate", blank=True, null=True, on_delete=models.SET_NULL
    )
    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"
    )
    router = models.ForeignKey(
        "Router", blank=True, null=True, on_delete=models.SET_NULL
    )
    check_bgp_session_states = models.BooleanField(default=False)
    bgp_session_states_update = models.DateTimeField(blank=True, null=True)
    communities = models.ManyToManyField("Community", blank=True)

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

    class Meta:
        ordering = ["name"]
        permissions = [
            ("view_configuration", "Can view Internet Exchange's configuration"),
            ("deploy_configuration", "Can deploy Internet Exchange's configuration"),
        ]

    def get_absolute_url(self):
        return reverse("peering:internet_exchange_details", kwargs={"slug": self.slug})

    def get_peering_sessions_list_url(self):
        return reverse(
            "peering:internet_exchange_peering_sessions", kwargs={"slug": self.slug}
        )

    def get_peer_list_url(self):
        return reverse("peering:internet_exchange_peers", kwargs={"slug": self.slug})

    def get_peering_sessions(self):
        return self.internetexchangepeeringsession_set.all()

    def get_autonomous_systems(self):
        autonomous_systems = []

        for session in self.internetexchangepeeringsession_set.all():
            if session.autonomous_system not in autonomous_systems:
                autonomous_systems.append(session.autonomous_system)

        return autonomous_systems

    def is_peeringdb_valid(self):
        """
        Tells if the PeeringDB ID for this IX is still valid. This function
        will return true if the PeeringDB record for this IX is valid or if
        this IX does not have a Peering DB ID set. In any other cases, the
        return value will be false.
        """
        if self.peeringdb_id:
            peeringdb_record = PeeringDB().get_ix_network(self.peeringdb_id)
            if not peeringdb_record:
                return False

        return True

    def get_peeringdb_id(self):
        """
        Retrieves the PeeringDB ID for this IX based on the IP addresses that
        have been recorded. The ID of the PeeringDB record will be returned on
        success. In any other cases 0 will be returned.
        """
        network_ixlan = PeeringDB().get_ix_network_by_ip_address(
            ipv6_address=self.ipv6_address, ipv4_address=self.ipv4_address
        )

        return network_ixlan.id if network_ixlan else 0

    def get_prefixes(self):
        """
        Returns a list of prefixes found in PeeringDB for this IX.
        """
        return PeeringDB().get_prefixes_for_ix_network(self.peeringdb_id)

    def _get_configuration_variables(self):
        peers6 = {}
        peers4 = {}

        # Sort peering sessions based on IP protocol version
        for session in self.internetexchangepeeringsession_set.all():
            if session.ip_address_version == 6:
                if session.autonomous_system.asn not in peers6:
                    peers6[
                        session.autonomous_system.asn
                    ] = session.autonomous_system.to_dict()
                    peers6[session.autonomous_system.asn].update({"sessions": []})
                peers6[session.autonomous_system.asn]["sessions"].append(
                    session.to_dict()
                )

            if session.ip_address_version == 4:
                if session.autonomous_system.asn not in peers4:
                    peers4[
                        session.autonomous_system.asn
                    ] = session.autonomous_system.to_dict()
                    peers4[session.autonomous_system.asn].update({"sessions": []})
                peers4[session.autonomous_system.asn]["sessions"].append(
                    session.to_dict()
                )

        return {
            "my_asn": settings.MY_ASN,
            "internet_exchange": self.to_dict(),
            "peering_groups": [
                {"ip_version": 6, "peers": peers6},
                {"ip_version": 4, "peers": peers4},
            ],
        }

    def generate_configuration(self):
        if not self.configuration_template:
            return ""
        return self.configuration_template.render(self._get_configuration_variables())

    def get_available_peers(self):
        # Not linked to PeeringDB, cannot determine peers
        if not self.peeringdb_id:
            return None

        # Get the IX LAN we are belonging to
        api = PeeringDB()
        network_ixlan = api.get_ix_network(self.peeringdb_id)

        # Get all peering sessions currently existing
        existing_sessions = self.get_peering_sessions()
        ipv4_sessions = []
        ipv6_sessions = []
        for session in existing_sessions:
            ip = ipaddress.ip_address(session.ip_address)
            if ip.version == 6:
                ipv6_sessions.append(str(ip))
            elif ip.version == 4:
                ipv4_sessions.append(str(ip))
            else:
                self.logger.debug("peering session with strange ip: %s", ip)

        # Find all peers belonging to the same IX and order them by ASN
        # Exclude our own ASN and already existing sessions
        return PeerRecord.objects.filter(
            Q(network_ixlan__ixlan_id=network_ixlan.ixlan_id)
            & ~Q(network__asn=settings.MY_ASN)
            & (
                ~Q(network_ixlan__ipaddr6__in=ipv6_sessions)
                | ~Q(network_ixlan__ipaddr4__in=ipv4_sessions)
            )
        ).order_by("network__asn")

    def _import_peering_sessions(self, sessions=[], prefixes=[]):
        # No sessions or no prefixes, can't work with that
        if not sessions or not prefixes:
            return None

        # Values to be returned
        number_of_peering_sessions = 0
        number_of_autonomous_systems = 0
        ignored_autonomous_systems = []

        with transaction.atomic():
            # For each session check if the address fits in one of the prefixes
            for session in sessions:
                for prefix in prefixes:
                    # No point of checking if a session fits inside a prefix if
                    # they are not using the same IP version
                    if session["ip_address"].version is not prefix.version:
                        self.logger.debug(
                            "ip %s cannot fit in prefix %s (not same ip version) ignoring",
                            str(session["ip_address"]),
                            str(prefix),
                        )
                        continue

                    self.logger.debug(
                        "checking if ip %s fits in prefix %s",
                        str(session["ip_address"]),
                        str(prefix),
                    )

                    # If the address fits, create a new InternetExchangePeeringSession
                    # object and a new AutonomousSystem object if they does not exist
                    # already
                    if session["ip_address"] in prefix:
                        ip_address = str(session["ip_address"])
                        remote_asn = session["remote_asn"]
                        self.logger.debug(
                            "ip %s fits in prefix %s", ip_address, str(prefix)
                        )

                        if not InternetExchangePeeringSession.does_exist(
                            ip_address=ip_address, internet_exchange=self
                        ):
                            self.logger.debug(
                                "session %s with as%s does not exist",
                                ip_address,
                                remote_asn,
                            )

                            # Grab the AS, create it if it does not exist in
                            # the database yet
                            autonomous_system = AutonomousSystem.does_exist(remote_asn)
                            if not autonomous_system:
                                self.logger.debug(
                                    "as%s not present importing from peeringdb",
                                    remote_asn,
                                )
                                autonomous_system = AutonomousSystem.create_from_peeringdb(
                                    remote_asn
                                )

                                # Do not count the AS if it does not have a
                                # PeeringDB record
                                if autonomous_system:
                                    self.logger.debug("as%s created", remote_asn)
                                    number_of_autonomous_systems += 1
                                else:
                                    if remote_asn not in ignored_autonomous_systems:
                                        ignored_autonomous_systems.append(remote_asn)
                                    self.logger.debug(
                                        "could not create as%s, session %s ignored",
                                        remote_asn,
                                        ip_address,
                                    )

                            # Only add a peering session if we were able to
                            # actually use the AS it is linked to
                            if autonomous_system:
                                self.logger.debug("creating session %s", ip_address)
                                values = {
                                    "autonomous_system": autonomous_system,
                                    "internet_exchange": self,
                                    "ip_address": ip_address,
                                }
                                peering_session = InternetExchangePeeringSession(
                                    **values
                                )
                                peering_session.save()
                                number_of_peering_sessions += 1
                                self.logger.debug("session %s created", ip_address)
                        else:
                            self.logger.debug(
                                "session %s with as%s already exists",
                                ip_address,
                                remote_asn,
                            )
                    else:
                        self.logger.debug(
                            "ip %s do not fit in prefix %s",
                            str(session["ip_address"]),
                            str(prefix),
                        )

        return (
            number_of_autonomous_systems,
            number_of_peering_sessions,
            ignored_autonomous_systems,
        )

    def import_peering_sessions_from_router(self):
        log = 'ignoring peering session on {}, reason: "{}"'
        if not self.router:
            log = log.format(self.name.lower(), "no router attached")
        elif not self.router.platform:
            log = log.format(self.name.lower(), "router with unsupported platform")
        else:
            log = None

        # No point of discovering from router if platform is none or is not
        # supported.
        if log:
            self.logger.debug(log)
            return False

        # Build a list based on prefixes based on PeeringDB records
        prefixes = self.get_prefixes()
        # No prefixes found
        if not prefixes:
            self.logger.debug("no prefixes found for %s", self.name.lower())
            return None
        else:
            self.logger.debug(
                "found %s prefixes (%s) for %s",
                len(prefixes),
                ", ".join([str(prefix) for prefix in prefixes]),
                self.name.lower(),
            )

        # Gather all existing BGP sessions from the router connected to the IX
        bgp_sessions = self.router.get_napalm_bgp_neighbors()

        return self._import_peering_sessions(bgp_sessions, prefixes)

    def update_peering_session_states(self):
        # Check if we are able to get BGP details
        log = 'ignoring session states on {}, reason: "{}"'
        if not self.router:
            log = log.format(self.name.lower(), "no router attached")
        elif not self.router.can_napalm_get_bgp_neighbors_detail():
            log = log.format(
                self.name.lower(),
                "router with unsupported platform {}".format(self.router.platform),
            )
        elif not self.check_bgp_session_states:
            log = log.format(self.name.lower(), "check disabled")
        else:
            log = None

        # If we cannot check for BGP details, don't do anything
        if log:
            self.logger.debug(log)
            return False

        # Get all BGP sessions detail
        bgp_neighbors_detail = self.router.get_napalm_bgp_neighbors_detail()

        # An error occured, probably
        if not bgp_neighbors_detail:
            return False

        with transaction.atomic():
            for vrf, as_details in bgp_neighbors_detail.items():
                for asn, sessions in as_details.items():
                    # Check BGP sessions found
                    for session in sessions:
                        ip_address = session["remote_address"]
                        self.logger.debug(
                            "looking for session %s in %s",
                            ip_address,
                            self.name.lower(),
                        )

                        # Check if the BGP session is on this IX
                        peering_session = InternetExchangePeeringSession.does_exist(
                            internet_exchange=self, ip_address=ip_address
                        )
                        if peering_session:
                            # Get the BGP state for the session
                            state = session["connection_state"].lower()
                            received = session["received_prefix_count"]
                            advertised = session["advertised_prefix_count"]
                            self.logger.debug(
                                "found session %s in %s with state %s",
                                ip_address,
                                self.name.lower(),
                                state,
                            )

                            # Update fields
                            peering_session.bgp_state = state
                            peering_session.received_prefix_count = (
                                received if received > 0 else 0
                            )
                            peering_session.advertised_prefix_count = (
                                advertised if advertised > 0 else 0
                            )
                            # Update the BGP state of the session
                            if peering_session.bgp_state == BGP_STATE_ESTABLISHED:
                                peering_session.last_established_state = timezone.now()
                            peering_session.save()
                        else:
                            self.logger.debug(
                                "session %s in %s not found",
                                ip_address,
                                self.name.lower(),
                            )

            # Save last session states update
            self.bgp_session_states_update = timezone.now()
            self.save()

        return True

    def __str__(self):
        return self.name
class Connection(ChangeLoggedModel, TaggableModel):
    peeringdb_netixlan = models.ForeignKey(to="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=True,
        blank=True,
        null=True,
        validators=[AddressFamilyValidator(6)],
    )
    ipv4_address = InetAddressField(
        store_prefix_length=True,
        blank=True,
        null=True,
        validators=[AddressFamilyValidator(4)],
    )
    internet_exchange_point = models.ForeignKey(to="peering.InternetExchange",
                                                blank=True,
                                                null=True,
                                                on_delete=models.CASCADE)
    router = models.ForeignKey(to="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)
    config_context = models.JSONField(blank=True, null=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_view", args=[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.
        """

        # If data imported from PeeringDB doesn't have IPs set, ignore it
        if self.ipv4_address is None and self.ipv6_address is None:
            return None

        # Prepare value for database lookup
        ipaddr6 = (self.ipv6_address.ip
                   if hasattr(self.ipv6_address, "ip") else self.ipv6_address)
        ipaddr4 = (self.ipv4_address.ip
                   if hasattr(self.ipv4_address, "ip") else self.ipv4_address)

        try:
            netixlan = NetworkIXLan.objects.get(ipaddr6=ipaddr6,
                                                ipaddr4=ipaddr4)
            logger.debug(
                f"linked connection {self} (pk: {self.pk}) to peeringdb")
        except NetworkIXLan.DoesNotExist:
            logger.debug(
                f"linking connection {self} (pk: {self.pk}) to peeringdb failed"
            )
            return None

        self.peeringdb_netixlan = netixlan
        self.save()

        return netixlan