A topology costs a few lines of text and a few seconds. Nothing on
your machine is touched. When you have broken it beyond repair you type
del all and build it again.
A lan is an Open vSwitch bridge. A kvm guest has its own kernel. A cnt guest is a real container with a real network stack. The routing daemons are the ones a carrier runs, and they do not know they are in a demo. When OSPF reconverges after you cut a cable, that is OSPF reconverging.
The daemons, the kernels and the frames are those of real machines: a configuration works or fails in cloonix for the reason it does on them, the timing of the hardware aside (What cloonix is not, below).
Every demo leaves the network running when it ends, so the topology it built is there to be changed, broken and captured after the last line of the script.
The demos are chosen for failure modes where the symptom names a different layer than the cause. Stop OSPF in the dns demo and name resolution stops: the symptom is DNS, the cause is routing. Leave out the discard route in frrmix and every BGP session comes up, reports itself established, and carries nothing.
A counter inside a guest reports what that guest believes; a capture reports what crossed the wire. Cloonix puts a sniffer on any capturable interface and hands back only what matches a filter — and the filter is applied on the server, so what does not match never crosses the wire to you.
Two subscriptions with the same filter, one on each side of a tunnel or a translation, is the standard way here of showing that something was encrypted, dropped or rewritten. The grammar is a subset of tcpdump's, and its limits are part of what it shows: below IP it knows arp alone, so a demo about EAPOL or batman OGMs says so and captures unfiltered rather than inventing a filter that cannot exist.
Subject areas, then one python file per demo:
demos/<area>/<demo>.py. The docstring at the top of each
one says what the software is and what the demo does with it, then the
topology as ascii art, then the packet to catch. Its first line is what the
Demo panel of the canvas shows beside the name.
cloonix/cnt.py | two containers on one lan and a ping: the nine lines of the first run, played by a program |
cloonix/kvm.py | the same with two virtual machines |
cloonix/a2b.py | the cable that can be told to be bad: delay, loss and rate, measured on both wires |
cloonix/c2c.py | three servers, two c2c cables, one router between two subnets, and a ping that crosses them |
cloonix/nat.py | passt: the dhcp answer, the way out, and the loopback of the host reached from inside |
cloonix/nginx.py | a published port: a web page served from a container with no address on the host |
cloonix/snf.py | one noisy lan, four filters, four counts |
cloonix/mount.py | a host directory inside two containers with vmount=, one image in two roles with startup_env=, and what a delete leaves behind |
cloonix/limit.py | cnf cpu= ram=: a cpu and ram cap put on a running container and measured from inside its cgroup |
cloonix/systemd.py | one container with an init beside two without, and the inotify margin of the host |
cloonix/cntwifi.py | mac80211_hwsim, hostapd, wpa_supplicant — and no wire at all |
cloonix/cntkvmwifi.py | a container radio and a kvm radio on one air, suid-power the medium of both |
cloonix/kvmwifi.py | mac80211_hwsim of the guest kernels, the cloonix agent carrying the frames to suid-power, the medium of every air |
cloonix/phy.py | a real interface taken into a net, absorbed on one kvm, macvlanned on the other, each running its own cloonix; the kvm without its eth0 still carries a container's pings |
cloonix/realnet.py | a cloonix inside a kvm: a guest of the inner net becomes a station of the outer lan through a tap and a bridge |
switching/vlan.py | one trunk, two networks, and a tag that hides every filter above the link layer |
switching/stp.py | a loop, the storm it makes, and the protocol that blocks one port |
routing/frrspider.py frrspider_bgp.py frrmix.py | sixty OSPF routers, the same sixty in BGP, then four AS with OSPF inside and BGP between |
address/dhcp.py | kea, two lans, one server, and giaddr deciding the subnet |
address/ipv6.py | zebra: a router advertisement, and hosts that make their own address |
address/dns.py | bind9 and frr: a name that only resolves if OSPF is up |
availability/vrrp.py | keepalived: two routers, one address, and a gratuitous ARP |
security/nftables.py | a stateful filter, masquerade, dnat, and the counter that says which rule matched |
security/tls.py | openssl: one page over http and over https, and the bytes of both on the lan |
security/wg.py | wireguard: the same taps as every other demo, and a wire that goes blank |
services/proxy.py | haproxy: one request, two connections, and a health check |
services/smtp.py | postfix: an MX lookup, then a conversation in plain text |
services/mqtt.py | mosquitto: one message in, several out |
services/asterisk.py | a PBX, two SIP phones, and calls that carry audio |
address/knot.py | an authoritative server and the zone transfer that feeds a second |
address/ntp.py | a client disciplining its clock to a server with chrony |
address/unbound.py | a resolver that answers, caches, and can be caught doing it |
availability/cluster.py | two nodes, one service, and a failover when one dies |
routing/isis.py | the link-state protocol of the carriers, in frames with no IP header |
routing/babel.py | distance-vector routing that reroutes when a link comes and goes |
routing/batman.py | a mesh that routes at layer 2, below IP entirely |
routing/bird.py | the other routing daemon: OSPF the same, in a different program |
security/arp.py | dsniff: arp poisoning, a machine put in the middle, and the static entry that stops it |
security/ebpf.py | a program loaded into the kernel to drop packets at the wire |
security/ipsec.py | an encrypted tunnel between two different implementations |
security/openvpn.py | a point-to-point tunnel with one shared key |
security/radius.py | a server that says yes to the right password and no to the wrong |
security/suricata.py | an intrusion sensor that alerts on traffic it is told to watch |
security/tinc.py | a small mesh vpn where each node knows the others |
services/squid.py | a caching proxy that speeds up hits and enforces a policy |
switching/vxlan.py | iproute2: a layer 2 network carried over a routed core in udp 4789 |
switching/ovn.py | one logical switch spread across two hosts by an overlay |
watching/perf.py | iperf3 and mtr over a cable that is degraded step by step |
wifi/cnt_*.py kvm_*.py cntkvm_*.py | hostapd and wpa_supplicant: open, WPA2, WPA3, a bridge, roaming, a mesh, a home box, WPA2-Enterprise, a listener in monitor mode |
windows.py | a windows server guest, its firewall, and the ping it answers only when told to |
freebsd.py | a freebsd guest with the agent on a disc of its own |
cisco.py | three ios-xe routers driven over their serial console |
The cloonix area is the platform under itself rather than a third
party: the two first networks, an impairment cable, a server-to-server link, the
way out of an isolated network, a cloonix inside a kvm, the capture path, and the
emulated radios.
Twelve packages, each a few demos on one subject, ordered so that a package uses only what the ones before it showed. On every step: run the demo, read the canvas and its History popup, catch the packet, break one thing and repair it. The chapter Learning with cloonix of the user book gives each package its steps, its packet and its break.
| package | demos | what it shows | |
|---|---|---|---|
| 0 | The first network | cloonix/cnt.py cloonix/kvm.py | add, plug, ssh, sniff, del all |
| 1 | The cloonix items | cloonix/a2b.py snf.py nat.py c2c.py nginx.py mount.py limit.py systemd.py cntwifi.py kvmwifi.py cntkvmwifi.py | a shaped wire, the capture, the way out, three nets joined, the way in, mounts and caps, an init, the radios |
| 2 | One wire | switching/vlan.py stp.py | 802.1Q tags, a loop and spanning tree |
| 3 | Addresses, names, time | address/dhcp.py ipv6.py dns.py unbound.py knot.py ntp.py | a relay, an address made from an advertisement, udp 53 and tcp 53, udp 123 |
| 4 | Routing | routing/frrspider.py frrspider_bgp.py frrmix.py bird.py isis.py babel.py batman.py | OSPF, BGP, redistribution, a second daemon, IS-IS, rerouting, a layer 2 mesh in the kernel |
| 5 | Encryption and tunnels | security/tls.py wg.py openvpn.py tinc.py ipsec.py | https beside http, then what the middle wire shows before and after a tunnel |
| 6 | The edge | security/nftables.py suricata.py ebpf.py radius.py arp.py | filter, inspect, drop in the kernel, authenticate, and arp poisoning |
| 7 | Services | services/proxy.py squid.py smtp.py mqtt.py asterisk.py | proxies, mail, publish and subscribe, a phone call |
| 8 | Staying up | availability/vrrp.py cluster.py | an address that moves, a service that fails over |
| 9 | Overlays and the real network | switching/vxlan.py ovn.py cloonix/phy.py realnet.py watching/perf.py | a layer 2 net over a routed core, Geneve, a real interface taken in, a cloonix inside a kvm, where the throughput goes |
| 10 | Wifi | wifi/cnt_*.py kvm_*.py cntkvm_*.py | an open air, WPA2, WPA3, a bridge, roaming, a mesh, a home box, a login per user, a listener |
| 11 | Other guests | freebsd.py windows.py cisco.py | a BSD with the agent, a windows desktop, a router on its console |
It emulates a network, not hardware. Forwarding at line rate, ASIC behaviour,
optics, anything where the timing of the silicon is the subject — those are
outside it. An a2b shapes a cable enough to show what
TCP does about a bad line; it is not a channel model. The wifi radios are
mac80211_hwsim: the 802.11 state machine is real, association and
EAPOL are real, the propagation is not.