<POWER> investigation — why generated devices load powered off¶
Scope: pkt-core toolkit (tools/lab_api.py, tools/device_templates.json) and the
JASDF-AEWC enterprise generator (JASDF_AEWC_source.zip → build_jasdf_aewc.py).
Investigation only; no code was changed.
TL;DR: lab_api.add_device(..., power=True) never writes <POWER>true</POWER> — it only
ever writes <POWER>false</POWER> when power=False — so every device inherits whatever the
harvested model template in tools/device_templates.json happens to contain. 136 of the 137
templates ship true, but one (MultiLayerSwitch / 3650-24PS) ships <POWER>false</POWER>
(a harvest artefact: the source save had that switch switched off). All 10 3650-24PS instances
in the enterprise model are the backbone / regional-hub / datalink-aggregation / DC-core
switches, so they load off, and with them 76 of 326 links (23 %) — which is why the lab looks
dead at the aggregation layer even though 280 of 290 devices carry POWER=true. Nothing in the
generator has a device-level power pass; the only fix on record (power_cabled_ports()) is
port-level and cannot touch <ENGINE><POWER>.
1. Measurement¶
Method: pkt_core.decode() on each .pkt, then walk NETWORK/DEVICES/DEVICE/ENGINE and read
<TYPE kind model>, and <POWER> as ABSENT / true / false. The decoded files are
byte-identical (sha256 f34018be… / 400fe203…) to the generator's out/*.pkt, so this is
exactly what Packet Tracer loads.
1.1 JASDF_AEWC_JADGE.pkt (enterprise profile) — 290 devices¶
<ENGINE><POWER> |
devices | share |
|---|---|---|
true |
280 | 96.6 % |
false |
10 | 3.4 % |
| absent | 0 | 0 % |
| total | 290 |
Cross-tab by device kind:
| kind | true |
false |
absent | total |
|---|---|---|---|---|
| ASA | 4 | 0 | 0 | 4 |
| AccessPoint | 26 | 0 | 0 | 26 |
| CyberObserver | 1 | 0 | 0 | 1 |
| MultiLayerSwitch | 31 | 10 | 0 | 41 |
| Pc | 55 | 0 | 0 | 55 |
| Router | 67 | 0 | 0 | 67 |
| Server | 64 | 0 | 0 | 64 |
| Sniffer | 1 | 0 | 0 | 1 |
| Switch | 31 | 0 | 0 | 31 |
Cross-tab by (kind, model) — only the differing row shown in full; every other model is 100 % true:
| kind | model | true |
false |
total |
|---|---|---|---|---|
| MultiLayerSwitch | 3650-24PS | 0 | 10 | 10 |
| MultiLayerSwitch | 3560-24PS | 15 | 0 | 15 |
| MultiLayerSwitch | IE-9320 | 16 | 0 | 16 |
| ASA | 5505 | 4 | 0 | 4 |
| AccessPoint | AccessPoint-PT | 26 | 0 | 26 |
| Pc | PC-PT | 55 | 0 | 55 |
| Router | 2911 | 26 | 0 | 26 |
| Router | IR1101 | 16 | 0 | 16 |
| Router | ISR4321 | 10 | 0 | 10 |
| Router | ISR4331 | 15 | 0 | 15 |
| Server | Server-PT | 64 | 0 | 64 |
| Switch | 2960-24TT | 31 | 0 | 31 |
| CyberObserver | CyberObserver | 1 | 0 | 1 |
| Sniffer | Sniffer | 1 | 0 | 1 |
The 10 unpowered devices (identical list in both files):
JADGE-BB-SW-01, JADGE-BB-SW-02 (backbone switches)
REG-HUB-NORTH, REG-HUB-CENTRAL,
REG-HUB-WEST, REG-HUB-SW (regional hubs)
DATALINK-AGG-01, DATALINK-AGG-02 (datalink aggregation)
JADGE-DC-CORE-01, JADGE-DC-CORE-02 (data-centre core)
1.2 JASDF_AEWC_JADGE_standard.pkt — 199 devices¶
<ENGINE><POWER> |
devices | share |
|---|---|---|
true |
189 | 95.0 % |
false |
10 | 5.0 % |
| absent | 0 | 0 % |
| kind | true |
false |
total |
|---|---|---|---|
| ASA | 4 | 0 | 4 |
| AccessPoint | 6 | 0 | 6 |
| CyberObserver | 1 | 0 | 1 |
| MultiLayerSwitch | 21 | 10 | 31 |
| Pc | 38 | 0 | 38 |
| Router | 49 | 0 | 49 |
| Server | 50 | 0 | 50 |
| Sniffer | 1 | 0 | 1 |
| Switch | 19 | 0 | 19 |
Same 10 3650-24PS devices are false; all other models (3560-24PS, IE-9320, 2960-24TT,
2911, IR1101, ISR4321, ISR4331, PC-PT, Server-PT, AccessPoint-PT, 5505, …) are
100 % true.
1.3 Why this looks like "most of the lab is off"¶
The 10 unpowered switches are not random leaf devices — they are the aggregation layer:
| impact (enterprise) | value |
|---|---|
links touching a POWER=false device |
76 / 326 (23.3 %) |
| devices directly adjacent to an off device | 62 / 290 (21.4 %) |
| devices within 2 hops of an off device | 134 / 290 (46.2 %) |
| link degree of the off devices | BB-SW-01: 9, BB-SW-02: 11, REG-HUB-NORTH: 13, REG-HUB-CENTRAL: 11, REG-HUB-WEST: 9, REG-HUB-SW: 9, DATALINK-AGG-01: 10, DATALINK-AGG-02: 10, DC-CORE-01: 3, DC-CORE-02: 2 |
So the accurate statement is: the residual bug hits 10 of 290 devices (3.4 %) at the
<ENGINE><POWER> level, but those 10 are precisely the core/aggregation switches, so 23 % of all
links come up dark and nearly half the topology sits within two hops of an unpowered box — that
is the "dark icons / no link lights everywhere" impression. (Every other device is on; the amber
link lights on access ports in the first ~30 s are documented STP convergence behaviour, see
JASDF_AEWC_VALIDATION.md.)
Port-level state is already repaired in these files: all 2 454 ports in the enterprise model carry
<PORT><POWER>true</POWER> (MultiLayerSwitch 1 118, Switch 806, Router 323, Server 64, Pc 55,
AccessPoint 52, ASA 32, CyberObserver 2, Sniffer 2) — that is JASDF.power_cabled_ports() at work
(178 ports raised in the enterprise run, 122 in standard; see build/report_enterprise.json).
2. Root cause¶
One sentence: lab_api.add_device() only writes <POWER>false</POWER> and never writes
true, so device power is inherited verbatim from the harvested template — and the single
3650-24PS template in tools/device_templates.json ships <ENGINE><POWER>false</POWER>, which
poisons every core/aggregation switch built from it.
Chain of evidence:
-
tools/lab_api.py(add_device, line 259; power handling at lines 293–297):power=True(the default) writes nothing at all. The effective power state is 100 % the template's text. Note also the latent trap: for a template with no<POWER>,power=Truestill leaves the element absent — andpkt_core.models.network.Device.powered(src/pkt_core/models/network.py:243) reads absent as not powered. -
tools/device_templates.json— 137 model templates audited (gzipped XML blocks;POWERsits at child index 2 of<ENGINE>, right after<TYPE>/<NAME>):
template <ENGINE><POWER> |
models |
|---|---|
true |
136 |
false |
1 — MultiLayerSwitch / 3650-24PS |
| absent | 0 |
The 3650-24PS block still carries the harvested instance name Multilayer Switch0
(SYS_NAME=Switch, serial CAT1010C93B): it was harvested from a save in which that switch
was switched off, and the harvest normalised nothing.
- Generator (
build_jasdf_aewc.py): devices are placed throughlab_api.add_devicewith the defaultpower=True, which — per (1) — leaves the template text untouched. The only power-related repairs are: fix_serial()/_set_port()(lines ~1044–1072): port-levelPOWER/CLOCKRATEfor serial bearers;power_cabled_ports()(lines 1090–1105): port-level<POWER>trueon ports of cabled devices ("Some harvested templates ship with<POWER>false</POWER>on their ports — notably AccessPoint-PT").
There is no pass that touches <ENGINE><POWER>. The generator's own
docs/EVIDENCE.md/VALIDATION.md "fixed in the generator" note refers only to the
port-level variant of the same defect class; the device-level variant was never addressed.
- Result: exactly the 10
3650-24PSinstances inheritfalse— verified byte-identical in generator output and delivered file. Every other model happens to shiptrue, which is why 280 devices are on: by luck of the harvest, not by construction.
3. Semantics: absent vs true vs false¶
Corpus: ../corpus/internet/, CORPUS_INDEX.tsv with decode_status == OK.
Clean 20-file sample (first 20 OK files that decode to well-formed XML; 3 further OK-indexed
files fail XML parsing and were skipped): 230 devices, <ENGINE><POWER> = true for all 230,
absent 0.
| kind | true |
other |
|---|---|---|
| Pc | 84 | — |
| Switch | 45 | — |
| Power Distribution Device | 20 | — |
| Router | 21 | — |
| MCUComponent | 22 | — |
| Laptop | 14 | — |
| Server | 8 | — |
| Pda | 4 | — |
| Printer | 4 | — |
| ASA / AccessPoint / Cloud / HomeGateway / Hub / WirelessRouterNewGeneration | 2 / 1 / 1 / 1 / 2 / 1 | — |
Extended full scan of all 443 decodable OK files (5 819 devices) for semantics:
<ENGINE><POWER> |
devices |
|---|---|
true |
5 813 (99.90 %) |
false |
6 (0.10 %) — 4× Laptop in vaamonde__ccna-v7-200-301__Projeto.pkt, 2× Router in dishavp__Network-Infrastructure-PacketTracer__HSRP.pkt |
| absent | 0 |
Conclusions:
true= powered on. It is the state of every working device in every hand-authored lab. All 28 device kinds seen in the corpus carry the element, including PCs (1 951/1 951), servers (339/339), MCU/IoT components (53/53), access points (103/103) and Power Distribution Devices (473/473). So PCs, servers and IoT/MCU devices do have a POWER concept; the "end devices have no power switch" hypothesis is false at the file level.false= powered off — confirmed by the 6 corpus instances (deliberately switched-off laptops/spare routers in authored labs) and by the project's own verified experiment (docs/CAPABILITIES.md§2: "Settingfalsepowers the device off at load (verified: the attached link drops)").- absent = never produced by Packet Tracer. 0 of 5 819 corpus devices omit the element; PT
always writes it. An absent
<POWER>is therefore a generator artefact with no author-defined meaning; the toolkit's own model (Device.powered) andadd_device'spower=Falsepath both treat it as "off", and PT's behaviour on a missing element is unverified — treat absent as unsupported, not as "on".
Port-level <PORT><POWER> is a different field (per-interface state used by APs, serial
bearers, link lights). Corpus distribution shows it is normal for unused ports to be
false or absent — e.g. Router 2 213 true / 2 576 false, Pc 1 951 true / 1 809 false /
1 962 absent, Switch 23 188 true / 345 false, Server 339 true / 89 absent. It must not be
blanket-set to true (see fix below).
4. Proposed fix¶
4.1 tools/lab_api.py — make add_device total (the real fix)¶
Write <ENGINE><POWER> explicitly from the power parameter in both directions, so a
template can never leak a stale state through:
p = eng.find("POWER")
if p is None:
p = ET.Element("POWER")
eng.insert(eng.index(eng.find("NAME")) + 1, p) # canonical slot: TYPE, NAME, POWER, ...
p.text = "true" if power else "false"
- Applies to all device kinds — routers, switches, APs, PCs, servers, ASA, IoT/MCU
components: corpus evidence shows every kind carries
<ENGINE><POWER>andtrueis the only state of a working device. No kind should be exempted. - Create the element at its canonical position (child index 2, after
<NAME>) rather thanET.SubElementat the end, to match PT-authored documents; if the element already exists (all 137 current templates), only the text changes.
4.2 tools/device_templates.json — clean the poisoned template¶
Change <ENGINE><POWER>false</POWER> → true in the 3650-24PS block (and audit
harvest_models() so future harvests normalise device power to true, or at least record the
harvested state so a "was off in the donor save" flag cannot silently ship). This alone fixes
both delivered .pkt files.
4.3 Enterprise generator (build_jasdf_aewc.py)¶
- With 4.1 in place,
add_device(..., power=True)is sufficient. Defence in depth: add apower_all_devices()pass next topower_cabled_ports()inmain()that forces<ENGINE><POWER>text="true"on every placed device (all kinds), and keeppower=Falseonly for devices the lab intends to stage off (call it out explicitly, e.g. spare/standby gear). - Keep
power_cabled_ports()as-is for the port level: only cabled ports should be forcedtrue; unused router/PC ports legitimately carryfalse/absent in the corpus. - Add a validation assertion in
validate.py: no device may end with<ENGINE><POWER>∈ {false, absent} unless it is on an explicit "staged off" list.
4.4 What not to do¶
- Do not delete
<POWER>instead of writingtrue— PT never omits it and the toolkit reads absence as off. - Do not blanket-set every
<PORT><POWER>totrue(corpus: unused ports are normallyfalse/absent). The existing cabled-port rule is the correct scope. - Do not assume end devices are exempt: PCs/servers/MCU devices all carry
POWER=truein the corpus.
5. Residual unknowns¶
- Packet Tracer behaviour for a missing
<ENGINE><POWER>is unverified (no corpus instance exists). Recommendation above avoids the case entirely. - The 6 corpus
falsedevices were authored intentionally; no corpus file demonstrates what a deliberately powered-off switch looks like beyond the3650-24PSharvest artefact. - The port-level
POWERsemantics (why unused router ports arefalsewhile switch ports aretrue) is characterised empirically only; it does not affect the device-level fix.
Method notes: both .pkt files decoded with pkt_core.decode() (src/pkt_core), XML walked
with xml.etree.ElementTree; template audit decompressed the gz+base64 blocks of
tools/device_templates.json (137 models entries); corpus scans used
../corpus/CORPUS_INDEX.tsv decode_status == OK files from ../corpus/internet/.