Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ The format follows [Keep a Changelog](https://keepachangelog.com/); versions fol
let 11 to 33% of packets through, including during the full outage in the
`mobile-lte-to-3g` scenario, and the log and the summary described runs of loss that
did not exist. A saved "Reproduce:" command with this combination now repeats a run
that loses every packet.
that loses every packet. The tooltip of the "Loss runs" counter says that a zero can
also mean 100% loss.

- **With Asymmetry on, the upload's runs of loss are described too.** The log now says
when the upload loss cannot reach the number you set in runs that short, and how far
Expand Down
2 changes: 1 addition & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@
"tips.stat_lan": "Packets to/from the internet dropped in LAN mode.",
"tips.stat_local": "Packets to/from the local network dropped by \"Internet only\".",
"tips.stat_loss": "Packets dropped because of the configured Loss. Link outages are counted separately, under Link outage.",
"tips.stat_loss_runs": "How many runs of lost packets this session produced. Zero with \"Losses in a row\" set means the session was too short to see one, not that nothing was configured. At 100% loss every packet is lost, so there are no separate runs to count.",
"tips.stat_loss_runs": "How many runs of lost packets this session produced. Zero with \"Losses in a row\" set does not mean nothing was configured: either the session was too short to see a run, or the loss is 100%, so every packet is lost and there are no separate runs to count.",
"tips.stat_mtu": "Packets dropped as too large (MTU black hole).",
"tips.stat_nat": "Packets dropped after the NAT mapping expired.",
"tips.stat_overflow": "Packets dropped because the queue overflowed (heavy overload). This counter always covers ALL captured traffic, even when the view is narrowed to the target: these are packets the TOOL lost, and hiding the ones outside your target would hide its own damage.",
Expand Down
2 changes: 1 addition & 1 deletion lang/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@
"tips.stat_lan": "Pakiety do/od internetu odrzucone w trybie LAN.",
"tips.stat_local": "Pakiety do/od sieci lokalnej odrzucone przez „Tylko internet”.",
"tips.stat_loss": "Pakiety porzucone z powodu ustawionej Utraty. Przerwy w łączu mają własny licznik - Przerwa w łączu.",
"tips.stat_loss_runs": "Ile serii gubionych pakietów wyszło w tej sesji. Zero przy ustawionym polu \"Straty pod rząd\" znaczy, że sesja była za krótka, żeby zobaczyć choć jedną, a nie że nic nie było ustawione. Przy stracie 100% ginie każdy pakiet, więc nie ma osobnych serii do policzenia.",
"tips.stat_loss_runs": "Ile serii gubionych pakietów wyszło w tej sesji. Zero przy ustawionym polu \"Straty pod rząd\" nie znaczy, że nic nie było ustawione: albo sesja była za krótka, żeby zobaczyć choć jedną serię, albo strata wynosi 100%, więc ginie każdy pakiet i nie ma osobnych serii do policzenia.",
"tips.stat_mtu": "Pakiety odrzucone jako za duże (czarna dziura MTU).",
"tips.stat_nat": "Pakiety odrzucone po wygaśnięciu mapowania NAT.",
"tips.stat_overflow": "Pakiety porzucone, bo kolejka się przepełniła (silne przeciążenie). Ten licznik zawsze obejmuje CAŁY przechwycony ruch, nawet gdy widok jest zawężony do celu: to są pakiety zgubione przez NARZĘDZIE, a ukrycie tych spoza celu ukryłoby jego własne szkody.",
Expand Down
2 changes: 1 addition & 1 deletion lang/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@
"tips.stat_lan": "在局域网模式下,被丢弃的互联网数据包。",
"tips.stat_local": "因“仅互联网”模式而被丢弃的本地网络数据包。",
"tips.stat_loss": "因配置的“丢包”效果而被丢弃的数据包。链路中断会在“链路中断”中单独统计。",
"tips.stat_loss_runs": "本次会话产生了多少串连续丢包。如果设置了\"连续丢包\"却显示 0,说明会话太短,还没有出现一串,而不是没有设置。丢包率为 100% 时每个数据包都会丢失,因此没有可单独计数的丢包串。",
"tips.stat_loss_runs": "本次会话产生了多少串连续丢包。如果设置了\"连续丢包\"却显示 0,并不表示没有设置:要么会话太短,还没有出现一串,要么丢包率为 100%,每个数据包都会丢失,因此没有可单独计数的丢包串。",
"tips.stat_mtu": "因超过 MTU(MTU 黑洞)而被丢弃的数据包。",
"tips.stat_nat": "NAT 映射过期后被丢弃的数据包。",
"tips.stat_overflow": "因队列溢出(严重过载)而被丢弃的数据包。即使视图已收窄到目标,此计数器也始终覆盖所有已捕获流量,因为这些数据包是本工具丢失的。隐藏目标之外的部分会掩盖工具自身造成的损害。",
Expand Down
50 changes: 41 additions & 9 deletions tests/test_burst_loss.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,31 +491,63 @@ def test_total_loss_makes_one_draw_per_packet_like_the_chain_did():
rng.getstate() == expected.getstate(), "(the draw count moved)")


def _paced_drops(core, rng, start, packets=20000):
"""Both directions in turn, 0.1 s apart; returns (dropped, clock after).

Slow on purpose: the scenario below also caps the speed (32 KB/s up at its
slowest), and 1200 bytes every 0.2 s per direction stays under every cap it
sets - so each drop counted here is a LOSS drop, not the rate limiter's.
"""
dropped = 0
for i in range(packets):
now = start + i * 0.1
dropped += core.decide(1200, bool(i % 2), 5000, now, rng, remote_ip="1.2.3.4",
remote_port=443, is_tcp=True).drop
return dropped, start + packets * 0.1


def test_the_shipped_lte_to_3g_outage_loses_everything():
"""The case the review found in a file this project ships.

``mobile-lte-to-3g.json`` cuts the link at 60 s with ``"loss": 100`` and
inherits a run length of 8 from the step before, so its full outage let about
one packet in nine through - enough for a connection to live through it.
Driven the way a session drives it: scenario step, ``apply_settings``, engine,
core.

Driven the way a session drives it: every step applied in turn to ONE engine
through ``apply_settings``, the outage entered and left twice, because the
chain state carried from step to step is exactly what a single step on a fresh
core cannot show. Before the outage and after it the loss must be the step's
own number again, arriving in runs.
"""
from beantester.engine import BeanEngine
from beantester.scenario import load_scenario_file
from beantester.settings import DEFAULT_SETTINGS, apply_settings

scenario = load_scenario_file(os.path.join(ROOT, "scenarios",
"mobile-lte-to-3g.json"))
settings = scenario.settings_at(60.05, DEFAULT_SETTINGS)
outage = scenario.settings_at(60.05, DEFAULT_SETTINGS)
check("the step is still total loss with an inherited run length",
settings["loss"] == 100 and settings["loss_burst"] > 1,
f"(loss={settings['loss']}, run={settings['loss_burst']})")
outage["loss"] == 100 and outage["loss_burst"] > 1,
f"(loss={outage['loss']}, run={outage['loss_burst']})")
engine = BeanEngine()
apply_settings(engine, settings)
engine.core.reset_buckets(0.0)
dropped, _ = _drops(engine.core, packets=20000, alternate=True)
check("the outage drops every packet", dropped == 20000,
f"(dropped {dropped} of 20000)")
rng = random.Random(21)
clock = 0.0
for at in (45.05, 60.05, 68.05, 60.05, 68.05):
settings = scenario.settings_at(at, DEFAULT_SETTINGS)
apply_settings(engine, settings)
runs_before = engine.core.loss_bursts
dropped, clock = _paced_drops(engine.core, rng, clock)
share = 100.0 * dropped / 20000
if settings["loss"] >= 100:
check(f"at {at} s the outage drops every packet", dropped == 20000,
f"(dropped {dropped} of 20000)")
continue
check(f"at {at} s the loss is the step's own {settings['loss']}% again",
abs(share - settings["loss"]) <= 2.0, f"(delivered {share:.2f}%)")
check(f"at {at} s it arrives in runs again",
engine.core.loss_bursts > runs_before,
f"({engine.core.loss_bursts - runs_before} runs started)")


def _burst_lines(monkeypatch, **fields):
Expand Down
10 changes: 10 additions & 0 deletions tests/test_mutation_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2397,6 +2397,16 @@
" return (1.0, r, 1.0)",
"test": "test_total_loss_loses_every_packet_whatever_the_run_length",
},
{
# The shipped scenario walked step by step on one engine: the steps around
# its outage must deliver their own loss, so a chain derived wrongly shows
# up there even though the outage itself still drops everything.
"label": "burst loss: the steps around a scenario's outage deliver the wrong loss",
"file": "beantester/core.py",
"old": " p = loss * r / room",
"new": " p = r",
"test": "test_the_shipped_lte_to_3g_outage_loses_everything",
},
{
# The upload walks its own chain from its own loss, so it clamps on its
# own - and until P3-4 the apply log never asked about it.
Expand Down
Loading