diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a02a1464..2c3730f1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ v2.5.2 (FUTURE) * [#2707](https://github.com/digitalocean/netbox/issues/2707) - Correct permission evaluation for circuit termination cabling * [#2712](https://github.com/digitalocean/netbox/issues/2712) - Preserve list filtering after editing objects in bulk * [#2717](https://github.com/digitalocean/netbox/issues/2717) - Fix bulk deletion of tags +* [#2721](https://github.com/digitalocean/netbox/issues/2721) - Detect loops when tracing front/rear ports * [#2723](https://github.com/digitalocean/netbox/issues/2723) - Correct permission evaluation when bulk deleting tags --- diff --git a/netbox/dcim/exceptions.py b/netbox/dcim/exceptions.py new file mode 100644 index 000000000..e788c9b5f --- /dev/null +++ b/netbox/dcim/exceptions.py @@ -0,0 +1,5 @@ +class LoopDetected(Exception): + """ + A loop has been detected while tracing a cable path. + """ + pass diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 4dcd5f278..347f0c8b8 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -21,6 +21,7 @@ from utilities.managers import NaturalOrderingManager from utilities.models import ChangeLoggedModel from utilities.utils import serialize_object, to_meters from .constants import * +from .exceptions import LoopDetected from .fields import ASNField, MACAddressField from .managers import DeviceComponentManager, InterfaceManager @@ -88,7 +89,7 @@ class CableTermination(models.Model): class Meta: abstract = True - def trace(self, position=1, follow_circuits=False): + def trace(self, position=1, follow_circuits=False, cable_history=None): """ Return a list representing a complete cable path, with each individual segment represented as a three-tuple: [ @@ -133,6 +134,13 @@ class CableTermination(models.Model): if not self.cable: return [(self, None, None)] + # Record cable history to detect loops + if cable_history is None: + cable_history = [] + elif self.cable in cable_history: + raise LoopDetected() + cable_history.append(self.cable) + far_end = self.cable.termination_b if self.cable.termination_a == self else self.cable.termination_a path = [(self, self.cable, far_end)] @@ -140,7 +148,11 @@ class CableTermination(models.Model): if peer_port is None: return path - next_segment = peer_port.trace(position, follow_circuits) + try: + next_segment = peer_port.trace(position, follow_circuits, cable_history) + except LoopDetected: + return path + if next_segment is None: return path + [(peer_port, None, None)]