diff options
| author | Dimitri Staessens <dimitri@ouroboros.rocks> | 2026-03-21 19:55:30 +0100 |
|---|---|---|
| committer | Dimitri Staessens <dimitri@ouroboros.rocks> | 2026-03-21 20:00:03 +0100 |
| commit | fb667c406053a2121d8d1f16099a8063292b3f45 (patch) | |
| tree | 8546c33d0c56cd433620d179ba4da08abb81a168 | |
| parent | 3ab851644501e4906e91084a81e33e1a3cebd5cc (diff) | |
| download | rumba-fb667c406053a2121d8d1f16099a8063292b3f45.tar.gz rumba-fb667c406053a2121d8d1f16099a8063292b3f45.zip | |
rumba: Add duplicate, reorder and corrupt link quality parameters
Add Duplicate, Reorder and Corrupt classes modelling the corresponding
tc-netem impairments, and extend LinkQuality and EthDixLayer with
support for these new parameters alongside the existing delay, loss
and rate.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
| -rw-r--r-- | rumba/elements/topology.py | 208 |
1 files changed, 197 insertions, 11 deletions
diff --git a/rumba/elements/topology.py b/rumba/elements/topology.py index 1e2c1a6..3d11761 100644 --- a/rumba/elements/topology.py +++ b/rumba/elements/topology.py @@ -231,6 +231,50 @@ class EthDixLayer(Layer): new_quality = LinkQuality.clone(self.link_quality, rate=rate) self.link_quality = new_quality + def set_duplicate(self, duplicate=0, correlation=None): + """ + Set the duplication parameter of the underlying link. + Parameters as in :py:class:`.Duplicate` + + :param duplicate: duplication in percentage + :type duplicate: :py:class:`int` or :py:class:`float` + :param correlation: correlation in percentage + :type correlation: :py:class:`int` or :py:class:`float` + """ + new_dup = Duplicate(duplicate, correlation) + new_quality = LinkQuality.clone(self.link_quality, duplicate=new_dup) + self.link_quality = new_quality + + def set_reorder(self, reorder=0, correlation=None): + """ + Set the reorder parameter of the underlying link. + Parameters as in :py:class:`.Reorder` + + :param reorder: reorder probability in percentage + :type reorder: :py:class:`int` or :py:class:`float` + :param correlation: correlation in percentage + :type correlation: :py:class:`int` or :py:class:`float` + """ + new_reorder = Reorder(reorder, correlation) + new_quality = LinkQuality.clone( + self.link_quality, reorder=new_reorder) + self.link_quality = new_quality + + def set_corrupt(self, corrupt=0, correlation=None): + """ + Set the corruption parameter of the underlying link. + Parameters as in :py:class:`.Corrupt` + + :param corrupt: corruption probability in percentage + :type corrupt: :py:class:`int` or :py:class:`float` + :param correlation: correlation in percentage + :type correlation: :py:class:`int` or :py:class:`float` + """ + new_corrupt = Corrupt(corrupt, correlation) + new_quality = LinkQuality.clone( + self.link_quality, corrupt=new_corrupt) + self.link_quality = new_quality + def set_quality(self, delay, loss, rate): """ Configure the basic quality parameters of the underlying link. @@ -492,6 +536,111 @@ class Loss(object): return " ".join(opts) +class Duplicate(object): + """ + A class representing packet duplication on a link. + """ + def __init__(self, duplicate, correlation=None): + """ + Configure link duplication. + + :param duplicate: duplication in percentage + :type duplicate: :py:class:`int` or :py:class:`float` + :param correlation: correlation in percentage + :type correlation: :py:class:`int` or :py:class:`float` + """ + if duplicate and (duplicate < 0 or duplicate > 100): + raise ValueError("Duplicate needs to be between 0 and 100") + if correlation and (correlation < 0 or correlation > 100): + raise ValueError("Correlation needs to be between 0 and 100") + self._duplicate = duplicate + self._correlation = correlation + + @property + def duplicate(self): + return self._duplicate + + @property + def correlation(self): + return self._correlation + + def build_command(self): + opts = ["duplicate %f%%" % self.duplicate] + if self.correlation: + opts.append("%f%%" % self.correlation) + return " ".join(opts) + + +class Reorder(object): + """ + A class representing packet reordering on a link. + """ + def __init__(self, reorder, correlation=None): + """ + Configure link reordering. + + :param reorder: reorder probability in percentage + :type reorder: :py:class:`int` or :py:class:`float` + :param correlation: correlation in percentage + :type correlation: :py:class:`int` or :py:class:`float` + """ + if reorder and (reorder < 0 or reorder > 100): + raise ValueError("Reorder needs to be between 0 and 100") + if correlation and (correlation < 0 or correlation > 100): + raise ValueError("Correlation needs to be between 0 and 100") + self._reorder = reorder + self._correlation = correlation + + @property + def reorder(self): + return self._reorder + + @property + def correlation(self): + return self._correlation + + def build_command(self): + opts = ["reorder %f%%" % self.reorder] + if self.correlation: + opts.append("%f%%" % self.correlation) + return " ".join(opts) + + +class Corrupt(object): + """ + A class representing packet corruption on a link. + """ + def __init__(self, corrupt, correlation=None): + """ + Configure link corruption. + + :param corrupt: corruption probability in percentage + :type corrupt: :py:class:`int` or :py:class:`float` + :param correlation: correlation in percentage + :type correlation: :py:class:`int` or :py:class:`float` + """ + if corrupt and (corrupt < 0 or corrupt > 100): + raise ValueError("Corrupt needs to be between 0 and 100") + if correlation and (correlation < 0 or correlation > 100): + raise ValueError("Correlation needs to be between 0 and 100") + self._corrupt = corrupt + self._correlation = correlation + + @property + def corrupt(self): + return self._corrupt + + @property + def correlation(self): + return self._correlation + + def build_command(self): + opts = ["corrupt %f%%" % self.corrupt] + if self.correlation: + opts.append("%f%%" % self.correlation) + return " ".join(opts) + + class LinkQuality(object): """ A class representing the link quality. @@ -499,7 +648,8 @@ class LinkQuality(object): _active = set() @classmethod - def clone(cls, old_quality, delay=None, loss=None, rate=None): + def clone(cls, old_quality, delay=None, loss=None, rate=None, + duplicate=None, reorder=None, corrupt=None): """ Clone old_quality, updating with provided parameters. @@ -507,6 +657,9 @@ class LinkQuality(object): :param delay: Delay object or number (ms) :param loss: Loss object or number (%) :param rate: Rate in mbit + :param duplicate: Duplicate object or number (%) + :param reorder: Reorder object or number (%) + :param corrupt: Corrupt object or number (%) :return: a new :py:class:`.LinkQuality` instance. """ if delay is None: @@ -515,25 +668,44 @@ class LinkQuality(object): loss = old_quality.loss if rate is None: rate = old_quality.rate - return LinkQuality(delay, loss, rate) + if duplicate is None: + duplicate = old_quality.duplicate + if reorder is None: + reorder = old_quality.reorder + if corrupt is None: + corrupt = old_quality.corrupt + return LinkQuality(delay, loss, rate, duplicate, reorder, corrupt) - def __init__(self, delay=None, loss=None, rate=None): + def __init__(self, delay=None, loss=None, rate=None, + duplicate=None, reorder=None, corrupt=None): """ Link quality configuration. :param delay: Delay object or number (ms) :param loss: Loss object or number (%) :param rate: Rate in mbit + :param duplicate: Duplicate object or number (%) + :param reorder: Reorder object or number (%) + :param corrupt: Corrupt object or number (%) """ if rate and not rate > 0: raise ValueError("Rate needs to be higher than 0") - if isinstance(delay, int) or isinstance(delay, float): + if isinstance(delay, (int, float)): delay = Delay(delay) - if isinstance(loss, int) or isinstance(loss, float): + if isinstance(loss, (int, float)): loss = Loss(loss) + if isinstance(duplicate, (int, float)): + duplicate = Duplicate(duplicate) + if isinstance(reorder, (int, float)): + reorder = Reorder(reorder) + if isinstance(corrupt, (int, float)): + corrupt = Corrupt(corrupt) self._delay = delay self._loss = loss self._rate = rate + self._duplicate = duplicate + self._reorder = reorder + self._corrupt = corrupt @property def delay(self): @@ -547,6 +719,18 @@ class LinkQuality(object): def rate(self): return self._rate + @property + def duplicate(self): + return self._duplicate + + @property + def reorder(self): + return self._reorder + + @property + def corrupt(self): + return self._corrupt + def build_commands(self, ipcp): netem_cmd = [] cmds = [] @@ -564,20 +748,22 @@ class LinkQuality(object): % (ipcp.ifname, self.rate)) qref = "parent 1:1" - if self.delay or self.loss: + netem_parts = [self.delay, self.loss, + self.duplicate, self.reorder, self.corrupt] + if any(netem_parts): netem_cmd.append( "tc qdisc add dev %s %s netem" % (ipcp.ifname, qref)) - if self.delay: - netem_cmd.append(self.delay.build_command()) - if self.loss: - netem_cmd.append(self.loss.build_command()) + for part in netem_parts: + if part: + netem_cmd.append(part.build_command()) cmds.append(" ".join(netem_cmd)) return cmds def apply(self, layer): - if not (self.delay or self.loss or self.rate): + if not (self.delay or self.loss or self.rate + or self.duplicate or self.reorder or self.corrupt): self.deactivate(layer) else: for ipcp in layer.ipcps: |
