bytesim 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bytesim-0.1.0/.gitignore +44 -0
- bytesim-0.1.0/CHANGELOG.md +35 -0
- bytesim-0.1.0/LICENSE +21 -0
- bytesim-0.1.0/PKG-INFO +178 -0
- bytesim-0.1.0/README.md +139 -0
- bytesim-0.1.0/docs/architecture.md +186 -0
- bytesim-0.1.0/docs/file-format.md +45 -0
- bytesim-0.1.0/docs/guide.md +565 -0
- bytesim-0.1.0/docs/scripts.md +140 -0
- bytesim-0.1.0/docs/topology-format.md +220 -0
- bytesim-0.1.0/pyproject.toml +58 -0
- bytesim-0.1.0/src/bytesim/__init__.py +14 -0
- bytesim-0.1.0/src/bytesim/__main__.py +3 -0
- bytesim-0.1.0/src/bytesim/cli.py +45 -0
- bytesim-0.1.0/src/bytesim/constants.py +48 -0
- bytesim-0.1.0/src/bytesim/demos.py +74 -0
- bytesim-0.1.0/src/bytesim/model/__init__.py +5 -0
- bytesim-0.1.0/src/bytesim/model/addressing.py +160 -0
- bytesim-0.1.0/src/bytesim/model/base.py +57 -0
- bytesim-0.1.0/src/bytesim/model/cloning.py +249 -0
- bytesim-0.1.0/src/bytesim/model/equivalence.py +86 -0
- bytesim-0.1.0/src/bytesim/model/groups.py +133 -0
- bytesim-0.1.0/src/bytesim/model/ha.py +93 -0
- bytesim-0.1.0/src/bytesim/model/layer2.py +106 -0
- bytesim-0.1.0/src/bytesim/model/layout.py +203 -0
- bytesim-0.1.0/src/bytesim/model/naming.py +114 -0
- bytesim-0.1.0/src/bytesim/model/network.py +138 -0
- bytesim-0.1.0/src/bytesim/model/routing.py +117 -0
- bytesim-0.1.0/src/bytesim/model/stp.py +315 -0
- bytesim-0.1.0/src/bytesim/model/types.py +107 -0
- bytesim-0.1.0/src/bytesim/py.typed +0 -0
- bytesim-0.1.0/src/bytesim/scripting.py +108 -0
- bytesim-0.1.0/src/bytesim/synthesis.py +644 -0
- bytesim-0.1.0/src/bytesim/topology.py +517 -0
- bytesim-0.1.0/src/bytesim/tui/__init__.py +4 -0
- bytesim-0.1.0/src/bytesim/tui/app.py +161 -0
- bytesim-0.1.0/src/bytesim/tui/cables.py +58 -0
- bytesim-0.1.0/src/bytesim/tui/canvas.py +489 -0
- bytesim-0.1.0/src/bytesim/tui/command_input.py +29 -0
- bytesim-0.1.0/src/bytesim/tui/commands/__init__.py +1 -0
- bytesim-0.1.0/src/bytesim/tui/commands/addressing.py +235 -0
- bytesim-0.1.0/src/bytesim/tui/commands/base.py +43 -0
- bytesim-0.1.0/src/bytesim/tui/commands/groups.py +215 -0
- bytesim-0.1.0/src/bytesim/tui/commands/history.py +134 -0
- bytesim-0.1.0/src/bytesim/tui/commands/interpreter.py +85 -0
- bytesim-0.1.0/src/bytesim/tui/commands/naming.py +68 -0
- bytesim-0.1.0/src/bytesim/tui/commands/registry.py +28 -0
- bytesim-0.1.0/src/bytesim/tui/commands/scripting.py +54 -0
- bytesim-0.1.0/src/bytesim/tui/commands/switching.py +192 -0
- bytesim-0.1.0/src/bytesim/tui/commands/topology.py +255 -0
- bytesim-0.1.0/src/bytesim/tui/commands/traffic.py +312 -0
- bytesim-0.1.0/src/bytesim/tui/help.py +50 -0
- bytesim-0.1.0/src/bytesim/tui/inspector.py +115 -0
- bytesim-0.1.0/src/bytesim/tui/theme.py +12 -0
- bytesim-0.1.0/tests/conftest.py +20 -0
- bytesim-0.1.0/tests/test_addressing.py +55 -0
- bytesim-0.1.0/tests/test_app.py +215 -0
- bytesim-0.1.0/tests/test_cloning.py +145 -0
- bytesim-0.1.0/tests/test_groups.py +51 -0
- bytesim-0.1.0/tests/test_ha.py +49 -0
- bytesim-0.1.0/tests/test_naming.py +46 -0
- bytesim-0.1.0/tests/test_stp.py +79 -0
- bytesim-0.1.0/tests/test_synthesis.py +173 -0
- bytesim-0.1.0/tests/test_topology.py +48 -0
- bytesim-0.1.0/tests/test_topology_format.py +223 -0
bytesim-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Packaging
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
.eggs/
|
|
12
|
+
wheels/
|
|
13
|
+
pip-wheel-metadata/
|
|
14
|
+
|
|
15
|
+
# Virtual environments
|
|
16
|
+
.venv/
|
|
17
|
+
venv/
|
|
18
|
+
env/
|
|
19
|
+
ENV/
|
|
20
|
+
|
|
21
|
+
# Test and tool caches
|
|
22
|
+
.pytest_cache/
|
|
23
|
+
.mypy_cache/
|
|
24
|
+
.ruff_cache/
|
|
25
|
+
.pyre/
|
|
26
|
+
.tox/
|
|
27
|
+
.nox/
|
|
28
|
+
.coverage
|
|
29
|
+
.coverage.*
|
|
30
|
+
coverage.xml
|
|
31
|
+
htmlcov/
|
|
32
|
+
.hypothesis/
|
|
33
|
+
|
|
34
|
+
# Editors and OS
|
|
35
|
+
.vscode/
|
|
36
|
+
.idea/
|
|
37
|
+
*.swp
|
|
38
|
+
*.swo
|
|
39
|
+
*~
|
|
40
|
+
.DS_Store
|
|
41
|
+
Thumbs.db
|
|
42
|
+
|
|
43
|
+
# ByteSim's default save file
|
|
44
|
+
bytesim.json
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (unreleased)
|
|
4
|
+
|
|
5
|
+
First release.
|
|
6
|
+
|
|
7
|
+
- Gateways, routers, switches and computers with any number of ports or NICs;
|
|
8
|
+
cabling by typing device ids.
|
|
9
|
+
- Automatic layered layout; moving devices with the keyboard or mouse; panning.
|
|
10
|
+
- Nestable groups drawn as frames.
|
|
11
|
+
- Addressing as (network, 8-bit host); MACs named after their port; router
|
|
12
|
+
autoconfiguration, animated DHCP, ad-hoc fallback, static addresses.
|
|
13
|
+
- Routing through gateways and between routers (cheapest path in cable hops),
|
|
14
|
+
with ARP shown for each hop and MAC learning on switches.
|
|
15
|
+
- Ping, unicast and multicast with packet animation; graph mode for pure
|
|
16
|
+
connectivity.
|
|
17
|
+
- Cable roles (data, control, fabric) and failure injection.
|
|
18
|
+
- HA router clusters: virtual gateways, failover, split-brain detection,
|
|
19
|
+
quorum and witness policies.
|
|
20
|
+
- BFS/DFS renumbering, one-shot or automatic (`renumber [auto] bfs|dfs [root]`).
|
|
21
|
+
- Spanning tree (root bridges, blocked ports, failover, priorities), real
|
|
22
|
+
flooding, and broadcast storms with MAC flapping; `broadcast`, `stp`,
|
|
23
|
+
`demo loop`.
|
|
24
|
+
- `clone <group>`: copy a group's devices, nested groups and internal cables.
|
|
25
|
+
- Declarative YAML topologies (`bytesim/topology-v1`): templates, instances,
|
|
26
|
+
link macros; `load` reads them and `export` writes them (compact or exact).
|
|
27
|
+
- Auto-numbering with `prefix(n)` (`s(2)`, `fab(1)`) in topology files and in
|
|
28
|
+
`group` / `clone`.
|
|
29
|
+
- `ports copy a(x, y) b(y, z)`: give a group another group's outside cabling onto new
|
|
30
|
+
endpoints; `copy a(x, y) b(y, z)` does it while cloning.
|
|
31
|
+
- `export script [readable|shortest|explicit] [file]`: synthesize the console commands that
|
|
32
|
+
rebuild the network, reusing repeated groups with clone / copy / ports copy; every
|
|
33
|
+
script is verified by replaying it. `run <file>` replays scripts.
|
|
34
|
+
- Multi-level undo and redo (`undo [n]`, `redo [n]`, `history`, `u` / `U`), up to 100 steps.
|
|
35
|
+
- JSON save and load; a `bytesim` command with `--demo` and `--run`.
|
bytesim-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael Yazdani
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
bytesim-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: bytesim
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A text-mode network simulator: cable up gateways, routers, switches and computers, then watch DHCP, routing, multicast and HA failover in your terminal.
|
|
5
|
+
Project-URL: Homepage, https://github.com/BytesAndCoffee/bytesim
|
|
6
|
+
Project-URL: Documentation, https://github.com/BytesAndCoffee/bytesim/tree/main/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/BytesAndCoffee/bytesim
|
|
8
|
+
Project-URL: Issues, https://github.com/BytesAndCoffee/bytesim/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/BytesAndCoffee/bytesim/blob/main/CHANGELOG.md
|
|
10
|
+
Author: Michael Yazdani
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: dhcp,education,network,networking,routing,simulator,textual,tui
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Intended Audience :: Education
|
|
18
|
+
Classifier: Intended Audience :: System Administrators
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
27
|
+
Classifier: Topic :: Education
|
|
28
|
+
Classifier: Topic :: System :: Networking
|
|
29
|
+
Classifier: Typing :: Typed
|
|
30
|
+
Requires-Python: >=3.10
|
|
31
|
+
Requires-Dist: pyyaml>=6.0
|
|
32
|
+
Requires-Dist: textual>=6.0
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: build; extra == 'dev'
|
|
35
|
+
Requires-Dist: pyflakes; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
37
|
+
Requires-Dist: twine; extra == 'dev'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# ByteSim
|
|
41
|
+
|
|
42
|
+
[](https://github.com/BytesAndCoffee/bytesim/actions/workflows/ci.yml)
|
|
43
|
+
[](https://pypi.org/project/bytesim/)
|
|
44
|
+
[](https://pypi.org/project/bytesim/)
|
|
45
|
+
|
|
46
|
+
**A network simulator that runs in your terminal.** Add gateways, routers,
|
|
47
|
+
switches and computers, cable them together by typing device ids, and watch
|
|
48
|
+
DHCP, routing, multicast and router failover happen packet by packet.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
╭┄ rfab1 · HA ┄┄┄┄┄┄┄┄┄┄┄┄╮
|
|
52
|
+
┆ ┌────────┐ ┌────────┐ ┆
|
|
53
|
+
┆ │ r1 │ │ r2 │ ┆
|
|
54
|
+
┆ │ ●●○●●● │ │ ●●○●●● │ ┆
|
|
55
|
+
┆ └─┬┬─┬┬┬─┘ └─┬┬─┬┬┬─┘ ┆
|
|
56
|
+
╰┄┄┄││┄┃║║┄┄┄┄┄┄┄┄││┄┃║║┄┄┄╯
|
|
57
|
+
┌──────────────┘└─┴┴┼──────────┼┼─┴┤║
|
|
58
|
+
│ ┌─────┴──────────┴┴──┼┴────────────┐
|
|
59
|
+
╭┄┄┄┄│ fab1 ┄┄┄┄┄┄┄│┄┄┄┄┄┄┄╮ ╭┄┄┄┄│ fab2 ┄┄┄┄┄┄┄│┄┄┄┄┄┄┄╮
|
|
60
|
+
┆ ┌──┴─────┐ ┌──┴─────┐ ┆ ┆ ┌──┴─────┐ ┌──┴─────┐ ┆
|
|
61
|
+
┆ │ s1 │ │ s2 │ ┆ ┆ │ s3 │ │ s4 │ ┆
|
|
62
|
+
┆ │ ●●●●○○ │ │ ●●●○○○ │ ┆ ┆ │ ●●●●○○ │ │ ●●●○○○ │ ┆
|
|
63
|
+
┆ └─┬─┬┬───┘ └─┬─┬────┘ ┆ ┆ └─┬─┬┬───┘ └─┬─┬────┘ ┆
|
|
64
|
+
╰┄┄┄│┄││┄┄┄┄┄┄┄┄┄┄│┄│┄┄┄┄┄┄╯ ╰┄┄┄│┄││┄┄┄┄┄┄┄┄┄┄│┄│┄┄┄┄┄┄╯
|
|
65
|
+
└─●•••─────┬─┘ │ │ ││ │ └─────┐
|
|
66
|
+
┌─┴──┐ ┌┴───┐ ┌────┐ ┌─┴──┐ ┌────┐ ┌────┐
|
|
67
|
+
│ c1 │ │ c2 │ │ c3 │ │ c4 │ │ c5 │ │ c6 │
|
|
68
|
+
└────┘ └────┘ └────┘ └────┘ └────┘ └────┘
|
|
69
|
+
(1,10) (1,11) (1,12) (2,10) (2,11) (2,12)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
ByteSim keeps what matters about networking (graphs, broadcast domains,
|
|
73
|
+
forwarding, failure) and leaves out the detail that gets in the way:
|
|
74
|
+
|
|
75
|
+
- **Devices with real ports.** Routers and switches can have any number of
|
|
76
|
+
ports, and computers can have several network cards. Every port shows on
|
|
77
|
+
the device as `○` (free) or `●` (in use).
|
|
78
|
+
- **Cabling by typing.** `r1 s2` connects two devices using their first free
|
|
79
|
+
ports. `r1:3 s2:8` picks the ports.
|
|
80
|
+
- **Automatic layout**, with arrow keys or the mouse to move things yourself.
|
|
81
|
+
- **Simple addressing.** An address is a pair: a network id and an 8-bit host
|
|
82
|
+
number, like `(2,14)`. MAC addresses name the physical port: `R1A` is port 1
|
|
83
|
+
of router r1.
|
|
84
|
+
- **DHCP and ad-hoc addressing**, animated as broadcast-then-reply.
|
|
85
|
+
- **Routing** through gateways and between routers, with ARP shown for every hop.
|
|
86
|
+
- **Unicast, ping and multicast.** With multicast you can see the packet copied
|
|
87
|
+
wherever the paths split.
|
|
88
|
+
- **Cable roles** (data, control, fabric) and failures you can trigger by hand.
|
|
89
|
+
- **Switching loops**: spanning tree with root bridges and blocked ports, and
|
|
90
|
+
real broadcast storms (frames multiplying, MAC tables flapping) when STP is off.
|
|
91
|
+
- **HA router clusters** with a shared virtual gateway, failover, split-brain,
|
|
92
|
+
and quorum or witness policies.
|
|
93
|
+
- **Nested groups** drawn as labelled frames, which you can **clone** along with
|
|
94
|
+
their internal cables.
|
|
95
|
+
- **BFS/DFS renumbering**, once or automatically after every change, so ids
|
|
96
|
+
follow the network's shape.
|
|
97
|
+
- **Graph mode**, which ignores addressing so you can explore pure connectivity.
|
|
98
|
+
- **Topology files.** Describe networks in YAML with reusable templates and link
|
|
99
|
+
macros (`mesh: [routers, switches]`), then load or export them.
|
|
100
|
+
- **Construction scripts.** `export script` writes the console commands that
|
|
101
|
+
rebuild your network, reusing repeated parts with `clone` and `copy`. Each
|
|
102
|
+
script is checked by replaying it before you get it, and `run` replays it.
|
|
103
|
+
|
|
104
|
+
## Install
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
pip install bytesim
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Requires Python 3.10+ and a terminal with Unicode and colour support.
|
|
111
|
+
|
|
112
|
+
## Quick start
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
bytesim --demo basic # a small routed network
|
|
116
|
+
bytesim --demo ha # a clustered router pair between two fabrics
|
|
117
|
+
bytesim --demo loop # a switch loop with STP off: set off a storm
|
|
118
|
+
bytesim mynet.json # open a saved network
|
|
119
|
+
bytesim campus.yaml # build one from a topology file
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Then, at the command line at the bottom of the screen:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
add r 4 add a router with 4 ports → r1
|
|
126
|
+
add s 8 add an 8-port switch → s1
|
|
127
|
+
add c 1 3 add three computers with 1 NIC → c1 c2 c3
|
|
128
|
+
r1 s1 cable r1 to s1
|
|
129
|
+
s1 c1 cable s1 to c1 (c1 gets an address by DHCP)
|
|
130
|
+
ping c1 c2 watch the echo request and reply
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Type `help` for every command. Press `Esc` to switch from the command line to
|
|
134
|
+
the canvas, where the arrow keys move the selected device and `Tab` cycles
|
|
135
|
+
through devices.
|
|
136
|
+
|
|
137
|
+
## Documentation
|
|
138
|
+
|
|
139
|
+
- [User guide](docs/guide.md): concepts, every command, keys, worked examples
|
|
140
|
+
- [Architecture](docs/architecture.md): how the code is organised, and how to add a command
|
|
141
|
+
- [Topology format](docs/topology-format.md): templates and link macros in YAML
|
|
142
|
+
- [Construction scripts](docs/scripts.md): `export script` and `run`, with a tutorial
|
|
143
|
+
- [Save file format](docs/file-format.md)
|
|
144
|
+
- [Changelog](CHANGELOG.md)
|
|
145
|
+
|
|
146
|
+
## Using the model from Python
|
|
147
|
+
|
|
148
|
+
The simulation is a plain Python model with no UI dependency:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from bytesim import Network
|
|
152
|
+
|
|
153
|
+
net = Network()
|
|
154
|
+
net.add("r"); net.add("s"); net.add("c"); net.add("c")
|
|
155
|
+
net.connect("r1", None, "s1", None)
|
|
156
|
+
net.connect("s1", None, "c1", None)
|
|
157
|
+
net.connect("s1", None, "c2", None)
|
|
158
|
+
net.reconcile() # router autoconfig + DHCP
|
|
159
|
+
|
|
160
|
+
print(net.iface("c1", 1).addr) # (1, 10)
|
|
161
|
+
for src, dst, frames in net.route("c1", ("c2", 1)):
|
|
162
|
+
print(src, "→", dst, [d for d, _ in frames])
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Development
|
|
166
|
+
|
|
167
|
+
```sh
|
|
168
|
+
git clone https://github.com/BytesAndCoffee/bytesim
|
|
169
|
+
cd bytesim
|
|
170
|
+
python -m venv .venv && . .venv/bin/activate
|
|
171
|
+
pip install -e ".[dev]"
|
|
172
|
+
pytest
|
|
173
|
+
python -m build && twine check dist/*
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT © Michael Yazdani. See [LICENSE](LICENSE).
|
bytesim-0.1.0/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# ByteSim
|
|
2
|
+
|
|
3
|
+
[](https://github.com/BytesAndCoffee/bytesim/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/bytesim/)
|
|
5
|
+
[](https://pypi.org/project/bytesim/)
|
|
6
|
+
|
|
7
|
+
**A network simulator that runs in your terminal.** Add gateways, routers,
|
|
8
|
+
switches and computers, cable them together by typing device ids, and watch
|
|
9
|
+
DHCP, routing, multicast and router failover happen packet by packet.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
╭┄ rfab1 · HA ┄┄┄┄┄┄┄┄┄┄┄┄╮
|
|
13
|
+
┆ ┌────────┐ ┌────────┐ ┆
|
|
14
|
+
┆ │ r1 │ │ r2 │ ┆
|
|
15
|
+
┆ │ ●●○●●● │ │ ●●○●●● │ ┆
|
|
16
|
+
┆ └─┬┬─┬┬┬─┘ └─┬┬─┬┬┬─┘ ┆
|
|
17
|
+
╰┄┄┄││┄┃║║┄┄┄┄┄┄┄┄││┄┃║║┄┄┄╯
|
|
18
|
+
┌──────────────┘└─┴┴┼──────────┼┼─┴┤║
|
|
19
|
+
│ ┌─────┴──────────┴┴──┼┴────────────┐
|
|
20
|
+
╭┄┄┄┄│ fab1 ┄┄┄┄┄┄┄│┄┄┄┄┄┄┄╮ ╭┄┄┄┄│ fab2 ┄┄┄┄┄┄┄│┄┄┄┄┄┄┄╮
|
|
21
|
+
┆ ┌──┴─────┐ ┌──┴─────┐ ┆ ┆ ┌──┴─────┐ ┌──┴─────┐ ┆
|
|
22
|
+
┆ │ s1 │ │ s2 │ ┆ ┆ │ s3 │ │ s4 │ ┆
|
|
23
|
+
┆ │ ●●●●○○ │ │ ●●●○○○ │ ┆ ┆ │ ●●●●○○ │ │ ●●●○○○ │ ┆
|
|
24
|
+
┆ └─┬─┬┬───┘ └─┬─┬────┘ ┆ ┆ └─┬─┬┬───┘ └─┬─┬────┘ ┆
|
|
25
|
+
╰┄┄┄│┄││┄┄┄┄┄┄┄┄┄┄│┄│┄┄┄┄┄┄╯ ╰┄┄┄│┄││┄┄┄┄┄┄┄┄┄┄│┄│┄┄┄┄┄┄╯
|
|
26
|
+
└─●•••─────┬─┘ │ │ ││ │ └─────┐
|
|
27
|
+
┌─┴──┐ ┌┴───┐ ┌────┐ ┌─┴──┐ ┌────┐ ┌────┐
|
|
28
|
+
│ c1 │ │ c2 │ │ c3 │ │ c4 │ │ c5 │ │ c6 │
|
|
29
|
+
└────┘ └────┘ └────┘ └────┘ └────┘ └────┘
|
|
30
|
+
(1,10) (1,11) (1,12) (2,10) (2,11) (2,12)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
ByteSim keeps what matters about networking (graphs, broadcast domains,
|
|
34
|
+
forwarding, failure) and leaves out the detail that gets in the way:
|
|
35
|
+
|
|
36
|
+
- **Devices with real ports.** Routers and switches can have any number of
|
|
37
|
+
ports, and computers can have several network cards. Every port shows on
|
|
38
|
+
the device as `○` (free) or `●` (in use).
|
|
39
|
+
- **Cabling by typing.** `r1 s2` connects two devices using their first free
|
|
40
|
+
ports. `r1:3 s2:8` picks the ports.
|
|
41
|
+
- **Automatic layout**, with arrow keys or the mouse to move things yourself.
|
|
42
|
+
- **Simple addressing.** An address is a pair: a network id and an 8-bit host
|
|
43
|
+
number, like `(2,14)`. MAC addresses name the physical port: `R1A` is port 1
|
|
44
|
+
of router r1.
|
|
45
|
+
- **DHCP and ad-hoc addressing**, animated as broadcast-then-reply.
|
|
46
|
+
- **Routing** through gateways and between routers, with ARP shown for every hop.
|
|
47
|
+
- **Unicast, ping and multicast.** With multicast you can see the packet copied
|
|
48
|
+
wherever the paths split.
|
|
49
|
+
- **Cable roles** (data, control, fabric) and failures you can trigger by hand.
|
|
50
|
+
- **Switching loops**: spanning tree with root bridges and blocked ports, and
|
|
51
|
+
real broadcast storms (frames multiplying, MAC tables flapping) when STP is off.
|
|
52
|
+
- **HA router clusters** with a shared virtual gateway, failover, split-brain,
|
|
53
|
+
and quorum or witness policies.
|
|
54
|
+
- **Nested groups** drawn as labelled frames, which you can **clone** along with
|
|
55
|
+
their internal cables.
|
|
56
|
+
- **BFS/DFS renumbering**, once or automatically after every change, so ids
|
|
57
|
+
follow the network's shape.
|
|
58
|
+
- **Graph mode**, which ignores addressing so you can explore pure connectivity.
|
|
59
|
+
- **Topology files.** Describe networks in YAML with reusable templates and link
|
|
60
|
+
macros (`mesh: [routers, switches]`), then load or export them.
|
|
61
|
+
- **Construction scripts.** `export script` writes the console commands that
|
|
62
|
+
rebuild your network, reusing repeated parts with `clone` and `copy`. Each
|
|
63
|
+
script is checked by replaying it before you get it, and `run` replays it.
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
pip install bytesim
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Requires Python 3.10+ and a terminal with Unicode and colour support.
|
|
72
|
+
|
|
73
|
+
## Quick start
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
bytesim --demo basic # a small routed network
|
|
77
|
+
bytesim --demo ha # a clustered router pair between two fabrics
|
|
78
|
+
bytesim --demo loop # a switch loop with STP off: set off a storm
|
|
79
|
+
bytesim mynet.json # open a saved network
|
|
80
|
+
bytesim campus.yaml # build one from a topology file
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Then, at the command line at the bottom of the screen:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
add r 4 add a router with 4 ports → r1
|
|
87
|
+
add s 8 add an 8-port switch → s1
|
|
88
|
+
add c 1 3 add three computers with 1 NIC → c1 c2 c3
|
|
89
|
+
r1 s1 cable r1 to s1
|
|
90
|
+
s1 c1 cable s1 to c1 (c1 gets an address by DHCP)
|
|
91
|
+
ping c1 c2 watch the echo request and reply
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Type `help` for every command. Press `Esc` to switch from the command line to
|
|
95
|
+
the canvas, where the arrow keys move the selected device and `Tab` cycles
|
|
96
|
+
through devices.
|
|
97
|
+
|
|
98
|
+
## Documentation
|
|
99
|
+
|
|
100
|
+
- [User guide](docs/guide.md): concepts, every command, keys, worked examples
|
|
101
|
+
- [Architecture](docs/architecture.md): how the code is organised, and how to add a command
|
|
102
|
+
- [Topology format](docs/topology-format.md): templates and link macros in YAML
|
|
103
|
+
- [Construction scripts](docs/scripts.md): `export script` and `run`, with a tutorial
|
|
104
|
+
- [Save file format](docs/file-format.md)
|
|
105
|
+
- [Changelog](CHANGELOG.md)
|
|
106
|
+
|
|
107
|
+
## Using the model from Python
|
|
108
|
+
|
|
109
|
+
The simulation is a plain Python model with no UI dependency:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from bytesim import Network
|
|
113
|
+
|
|
114
|
+
net = Network()
|
|
115
|
+
net.add("r"); net.add("s"); net.add("c"); net.add("c")
|
|
116
|
+
net.connect("r1", None, "s1", None)
|
|
117
|
+
net.connect("s1", None, "c1", None)
|
|
118
|
+
net.connect("s1", None, "c2", None)
|
|
119
|
+
net.reconcile() # router autoconfig + DHCP
|
|
120
|
+
|
|
121
|
+
print(net.iface("c1", 1).addr) # (1, 10)
|
|
122
|
+
for src, dst, frames in net.route("c1", ("c2", 1)):
|
|
123
|
+
print(src, "→", dst, [d for d, _ in frames])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Development
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
git clone https://github.com/BytesAndCoffee/bytesim
|
|
130
|
+
cd bytesim
|
|
131
|
+
python -m venv .venv && . .venv/bin/activate
|
|
132
|
+
pip install -e ".[dev]"
|
|
133
|
+
pytest
|
|
134
|
+
python -m build && twine check dist/*
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
MIT © Michael Yazdani. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
ByteSim has two halves: a pure-Python **model** that knows nothing about the
|
|
4
|
+
screen, and a **Textual UI** that draws the model and turns typed commands
|
|
5
|
+
into model calls.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/bytesim/
|
|
9
|
+
├── __init__.py version, re-exports Network & friends
|
|
10
|
+
├── __main__.py python -m bytesim
|
|
11
|
+
├── cli.py argument parsing, starts the app
|
|
12
|
+
├── constants.py device kinds, sizes, token regexes, address constants
|
|
13
|
+
├── demos.py ready-made networks (basic, ha, loop)
|
|
14
|
+
├── topology.py the YAML topology format: load_topology(), dump_topology()
|
|
15
|
+
├── scripting.py ScriptRunner / replay(): run command scripts headlessly
|
|
16
|
+
├── synthesis.py export script: plan, find reuse, render, verify by replay
|
|
17
|
+
├── model/ ── no UI imports below this line ──
|
|
18
|
+
│ ├── types.py Device, Link, Iface, NetError, fmt()
|
|
19
|
+
│ ├── base.py NetworkBase: the state + basic lookups
|
|
20
|
+
│ ├── groups.py nestable groups, frame bounds
|
|
21
|
+
│ ├── cloning.py clone_group(): devices, nested groups, internal cables
|
|
22
|
+
│ ├── layer2.py usable cables, broadcast domains, frame paths, MAC learning, graph-mode BFS
|
|
23
|
+
│ ├── addressing.py router autoconfig, DHCP, ad-hoc, reconcile()
|
|
24
|
+
│ ├── routing.py host → gateway → cheapest router path
|
|
25
|
+
│ ├── ha.py clusters: partitions, policies, virtual-gateway owners
|
|
26
|
+
│ ├── layout.py automatic arrangement
|
|
27
|
+
│ ├── naming.py BFS/DFS traversal, renumber()
|
|
28
|
+
│ ├── stp.py spanning tree, loop detection, flooding, storms
|
|
29
|
+
│ ├── equivalence.py canonical form of a network; differences()
|
|
30
|
+
│ └── network.py Network = all mixins + editing + JSON
|
|
31
|
+
└── tui/
|
|
32
|
+
├── app.py ByteSimApp: layout, dispatch(), changed()
|
|
33
|
+
├── canvas.py NetCanvas: drawing, mouse/keys, packet animation
|
|
34
|
+
├── cables.py where cables run, line glyphs
|
|
35
|
+
├── inspector.py side-panel text
|
|
36
|
+
├── command_input.py the command line with history
|
|
37
|
+
├── help.py `help` text
|
|
38
|
+
├── theme.py colours
|
|
39
|
+
└── commands/
|
|
40
|
+
├── registry.py @command decorator, collect()
|
|
41
|
+
├── base.py CommandHost: what handlers may use
|
|
42
|
+
├── topology.py add, link, del, ports, role, fail, files, mode…
|
|
43
|
+
├── groups.py group, ungroup, clone, cluster…
|
|
44
|
+
├── addressing.py ip, dhcp, release, adhoc, dhcpd (+ animated DHCP)
|
|
45
|
+
├── naming.py renumber (one-shot and auto mode)
|
|
46
|
+
├── switching.py broadcast, stp, storm reporting + flood animation
|
|
47
|
+
├── scripting.py export script, run
|
|
48
|
+
├── history.py undo / redo (snapshot per changing command)
|
|
49
|
+
├── interpreter.py tokens + dispatch (shared by the app and ScriptRunner)
|
|
50
|
+
└── traffic.py ping, unicast, multicast
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## The model
|
|
54
|
+
|
|
55
|
+
`Network` (in `model/network.py`) is assembled from mixins that each own one
|
|
56
|
+
concern and share the state defined in `NetworkBase`:
|
|
57
|
+
|
|
58
|
+
| State | Meaning |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `devices: dict[str, Device]` | `"r1"` → kind, number, port count, position, up/down |
|
|
61
|
+
| `links: list[Link]` | cables: two (device, port) ends, role, up/down |
|
|
62
|
+
| `groups: dict[str, list[str]]` | group → members (device ids or group names) |
|
|
63
|
+
| `ifaces: dict[str, Iface]` | `"c1:1"` → mode, network, host, gateway, how it was obtained |
|
|
64
|
+
| `clusters: dict[str, str]` | HA cluster (a group name) → split policy |
|
|
65
|
+
| `dhcp_off`, `mac_tables`, `counters`, `notes` | the rest |
|
|
66
|
+
|
|
67
|
+
The model's core questions are answered by a few functions:
|
|
68
|
+
|
|
69
|
+
- `usable(link)`: can this cable carry data? Not if it's down, a control
|
|
70
|
+
cable, either end is dead, STP is blocking either end, or a broadcast storm
|
|
71
|
+
has saturated it. Everything that forwards goes through this.
|
|
72
|
+
- `segment(dev, port)`: the broadcast domain seen from one interface (every
|
|
73
|
+
other interface reachable through switches alone).
|
|
74
|
+
- `l2path(src_iface, dst_iface)`: the frame path between two interfaces.
|
|
75
|
+
- `route(src, dst_iface)`: a list of **legs**, each one layer-2 hop between
|
|
76
|
+
addressed interfaces: `(from_iface, to_iface, frame_path)`.
|
|
77
|
+
- `ha_state(cluster)`: partitions, active sides, virtual-gateway owners, and
|
|
78
|
+
a health level.
|
|
79
|
+
- `stp_update()`: recomputes blocked ports (union-find over the switch graph,
|
|
80
|
+
nearest-the-root first) and ends storms whose loop is gone.
|
|
81
|
+
- `flood(iface)`: follows one broadcast, generation by generation. Copies are
|
|
82
|
+
counted per (cable, direction), so even an exponential storm is cheap. If
|
|
83
|
+
frames are still alive after `len(links) + 2` hops, it's a storm.
|
|
84
|
+
- `reconcile()`: runs `stp_update()`, then brings addresses in line with the
|
|
85
|
+
cabling, and returns notes describing what changed. The app calls it after every change.
|
|
86
|
+
- `auto_layout(width)`: positions every device.
|
|
87
|
+
- `traversal(order, root)` / `renumber(order, root)`: BFS/DFS visit order,
|
|
88
|
+
and renaming every device to match (returns `{old: new}`).
|
|
89
|
+
|
|
90
|
+
Code that holds on to a device across `changed()` should keep the
|
|
91
|
+
`Device` object, not its id: auto-renumbering may rename it (see `cmd_link`).
|
|
92
|
+
|
|
93
|
+
Device paths are lists of `(device_id, index_of_link_used_to_arrive)`, with
|
|
94
|
+
`-1` for the first device.
|
|
95
|
+
|
|
96
|
+
## The UI
|
|
97
|
+
|
|
98
|
+
`ByteSimApp` combines the four command mixins with Textual's `App`. The flow
|
|
99
|
+
for every command:
|
|
100
|
+
|
|
101
|
+
1. `on_input_submitted` → `run_command(line)`. Any `NetError` becomes a red
|
|
102
|
+
console line.
|
|
103
|
+
2. `dispatch(tokens)` pulls out `key=value` options and applies the shortcuts
|
|
104
|
+
(`r1` selects, `r1 s2` cables, a bare group name selects it). It then looks
|
|
105
|
+
the word up in the table built by `registry.collect()`.
|
|
106
|
+
3. The handler changes the model, logs what happened, and calls
|
|
107
|
+
`self.changed()`.
|
|
108
|
+
4. `changed()` runs `reconcile`, applies auto-renumbering if it's on, reports HA state changes, re-runs the layout
|
|
109
|
+
if auto-arrange is on, and redraws the canvas and inspector.
|
|
110
|
+
|
|
111
|
+
`NetCanvas.render()` rebuilds the picture every frame, in layers: group
|
|
112
|
+
frames, then cables, then group labels, then device boxes, then address
|
|
113
|
+
labels, then packets. Each cable cell keeps a bitmask of the directions it
|
|
114
|
+
connects in (N/E/S/W), so crossings and junctions get the right box-drawing
|
|
115
|
+
character. A cell used only by control cables uses the double-line set; one
|
|
116
|
+
used only by fabric cables uses the heavy set.
|
|
117
|
+
|
|
118
|
+
Packets are lists of screen cells. A timer moves every packet forward one
|
|
119
|
+
cell per tick and runs a callback when it arrives, which is how ping chains
|
|
120
|
+
its reply. Multicast copies share cells until their paths split, so a single
|
|
121
|
+
packet appears to fork.
|
|
122
|
+
|
|
123
|
+
## Scripts and synthesis
|
|
124
|
+
|
|
125
|
+
The command interpreter (`tui/commands/interpreter.py`) has no Textual
|
|
126
|
+
dependency. The app and `scripting.ScriptRunner` both use it.
|
|
127
|
+
|
|
128
|
+
- **`ScriptRunner`** is the real command mixins plus a stub canvas. It builds
|
|
129
|
+
structure and skips layout, re-addressing and animation, so replaying a
|
|
130
|
+
script is fast and uses exactly the app's command semantics.
|
|
131
|
+
- **`synthesis.synthesize(net, mode)`**:
|
|
132
|
+
1. Renders an explicit plan: devices in id order, with placeholders for
|
|
133
|
+
gaps, then groups, interfaces, cables, settings and failures.
|
|
134
|
+
2. Lists reuse candidates. `clone` candidates are checked against the exact
|
|
135
|
+
mapping `clone` would make. `copy` and `ports copy` candidates compare
|
|
136
|
+
boundary patterns.
|
|
137
|
+
3. Tries the best savings first. Each candidate is kept only if the
|
|
138
|
+
re-rendered script replays into a network with no
|
|
139
|
+
`equivalence.differences()` and is shorter.
|
|
140
|
+
- **The renderer** interleaves explicit `add`s and clones so every kind's ids
|
|
141
|
+
are created in increasing order, and finishes a clone's source (groups,
|
|
142
|
+
internal cables, settings) just before the clone.
|
|
143
|
+
|
|
144
|
+
## Adding a command
|
|
145
|
+
|
|
146
|
+
Add a method to the mixin that fits, or create a new mixin and add it to
|
|
147
|
+
`ByteSimApp`'s bases:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from .registry import command
|
|
151
|
+
|
|
152
|
+
class TopologyCommands(CommandHost):
|
|
153
|
+
@command("rename", "mv")
|
|
154
|
+
def cmd_rename(self, args: list[str], opts: dict[str, str]) -> None:
|
|
155
|
+
if len(args) != 2:
|
|
156
|
+
raise NetError("usage: rename <id> <new-id>")
|
|
157
|
+
... # change self.net
|
|
158
|
+
self.log_msg("renamed")
|
|
159
|
+
self.changed()
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Raise `NetError` for anything the user got wrong; it's shown in the console.
|
|
163
|
+
Put real behaviour in the model rather than the handler, so it can be tested
|
|
164
|
+
without the UI. Add a line to `tui/help.py` and to `docs/guide.md`.
|
|
165
|
+
|
|
166
|
+
## Tests
|
|
167
|
+
|
|
168
|
+
```sh
|
|
169
|
+
pytest
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
- `tests/test_topology.py`, `test_groups.py`, `test_addressing.py` and
|
|
173
|
+
`test_ha.py`, `test_naming.py`, `test_stp.py` and `test_cloning.py` exercise
|
|
174
|
+
the model directly, using the demo networks from
|
|
175
|
+
`conftest.py`.
|
|
176
|
+
- `tests/test_app.py` drives the real app headlessly with Textual's
|
|
177
|
+
`run_test()` and waits for packet animations to finish.
|
|
178
|
+
|
|
179
|
+
## Releasing
|
|
180
|
+
|
|
181
|
+
1. Bump `__version__` in `src/bytesim/__init__.py` and add a `CHANGELOG.md`
|
|
182
|
+
entry.
|
|
183
|
+
2. `python -m build` creates `dist/bytesim-X.Y.Z.tar.gz` and the wheel.
|
|
184
|
+
3. `twine check dist/*`
|
|
185
|
+
4. `twine upload --repository testpypi dist/*` to try it, then
|
|
186
|
+
`twine upload dist/*`.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Save file format
|
|
2
|
+
|
|
3
|
+
This is the complete JSON snapshot written by `save`. For the hand-writable,
|
|
4
|
+
template-based YAML format, see [topology-format.md](topology-format.md).
|
|
5
|
+
|
|
6
|
+
`save` writes, and `load` reads, a single JSON object. All keys except
|
|
7
|
+
`counters`, `devices` and `links` are optional, so older files still load.
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"counters": { "g": 1, "r": 2, "s": 3, "c": 10 },
|
|
12
|
+
"devices": [
|
|
13
|
+
{ "kind": "r", "num": 1, "nports": 6, "x": 64, "y": 1, "up": true }
|
|
14
|
+
],
|
|
15
|
+
"links": [
|
|
16
|
+
{ "a": "r1", "pa": 5, "b": "r2", "pb": 5, "role": "control", "up": true }
|
|
17
|
+
],
|
|
18
|
+
"groups": { "dc1": ["fab1", "rfab1"], "rfab1": ["r1", "r2"], "fab1": ["s1", "s2"] },
|
|
19
|
+
"clusters": { "rfab1": "witness" },
|
|
20
|
+
"ifaces": {
|
|
21
|
+
"c1:1": { "mode": "dhcp", "net": 1, "host": 10, "gw": 254, "via": "dhcp" }
|
|
22
|
+
},
|
|
23
|
+
"dhcp_off": ["r2"],
|
|
24
|
+
"stp_off": ["s4", "s5"],
|
|
25
|
+
"stp_priority": { "s7": 4096 }
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Key | Meaning |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `counters` | the last number used for each kind, so ids are never reused |
|
|
32
|
+
| `devices[]` | `kind` (`g`/`r`/`s`/`c`) and `num` make the id (`r1`). Also `nports`, canvas position `x`/`y`, and `up` (false = failed) |
|
|
33
|
+
| `links[]` | two ends (`a`/`pa`, `b`/`pb`; ports start at 1), `role` (`data`, `control`, `control-backup`, `fabric`) and `up` |
|
|
34
|
+
| `groups` | group → members, in order. A member is a device id or another group's name (groups nest) |
|
|
35
|
+
| `clusters` | groups that are HA clusters → split policy (`none`, `quorum`, `witness`) |
|
|
36
|
+
| `ifaces` | `"device:port"` → address config. `mode`: `dhcp`, `adhoc`, `static`, `auto` (routers) or `none`. `net`/`host`: the address. `gw`: the gateway's host number on the same network (254 = cluster virtual gateway). `via`: how the current address was obtained |
|
|
37
|
+
| `dhcp_off` | routers and gateways whose DHCP server is switched off |
|
|
38
|
+
| `stp_off` | switches with spanning tree switched off (every other switch runs it) |
|
|
39
|
+
| `stp_priority` | switch → bridge priority (default 32768; lower wins the root election) |
|
|
40
|
+
|
|
41
|
+
Blocked ports and storms aren't saved: blocked ports are recomputed when a file
|
|
42
|
+
is loaded, and a storm only starts when traffic is sent.
|
|
43
|
+
|
|
44
|
+
Entries that refer to missing devices or groups are dropped on load. An
|
|
45
|
+
address in an older format is also dropped, and recomputed from the cabling.
|