Network condition simulator for Linux routers. Injects latency, jitter, packet loss, reordering, and duplication per client IP using tc/netem/ifb.
Comes with built-in profiles for common network conditions (3G, LTE, Satellite, Starlink, etc.) and an interactive TUI to manage them. Profiles support asymmetric upload/download parameters to model real-world links.
lagsim sets up an HTB qdisc tree on your LAN interface with per-client classes and netem leaf qdiscs. Ingress traffic is redirected through an IFB device so both upload and download are conditioned independently.
LAN clients <──eth0──> router <──wan0──> internet
│
HTB + netem (egress/download)
IFB + netem (ingress/upload)
Each direction gets its own netem parameters, so profiles can model asymmetric links (e.g., DSL with fast download / slow upload, or cellular with higher uplink loss).
go build -o lagsim .
sudo cp lagsim /usr/local/bin/sudo lagsim| Key | Action |
|---|---|
↑/k, ↓/j |
Navigate client list |
Enter |
Open profile selection menu |
e |
Edit device name (requires MAC) |
r, Delete |
Remove profile from client |
Esc |
Cancel / back to list |
Ctrl+U |
Clear name in edit mode |
q, Ctrl+C |
Quit |
# List clients and their profiles
sudo lagsim list
# Show available profiles
sudo lagsim profiles
# Apply a profile to a client
sudo lagsim apply 192.168.1.100 3G
# Remove conditioning from a client
sudo lagsim remove 192.168.1.100
# Initialize tc infrastructure and restore saved assignments
sudo lagsim init
# Tear down all tc rules
sudo lagsim teardown
# Dump raw tc state for debugging
sudo lagsim status| Flag | Description |
|---|---|
-c, --config |
Config file path (default ~/.config/lagsim.yaml) |
--dry-run |
Print tc commands without executing |
-v, --verbose |
Verbose output |
On first run, lagsim auto-detects the LAN interface and subnet. If multiple interfaces are found, it prompts you to choose.
Each parameter is applied per-direction (egress + ingress), so effective RTT is roughly 2x the delay value. Asymmetric values show ▲ upload and ▼ download on separate lines.
| Profile | Delay | Jitter | Dist | Loss | Reorder | Slot | Rate |
|---|---|---|---|---|---|---|---|
| 3G | 100ms | ▲ 50ms ▼ 30ms |
paretonormal | ▲ 2.5% ▼ 1.5% |
– | 40ms 10ms | ▲ 0.5 Mbps ▼ 2 Mbps |
| LTE | 20ms | ▲ 8ms ▼ 5ms |
paretonormal | ▲ 1% ▼ 0.5% |
– | 10ms 3ms | ▲ 15 Mbps ▼ 50 Mbps |
| 5G | 5ms | 1ms | paretonormal | ▲ 0.1% ▼ 0.05% |
– | – | ▲ 100 Mbps ▼ 300 Mbps |
| Edge-2G | 150ms | ▲ 100ms ▼ 60ms |
paretonormal | ▲ 8% ▼ 5% |
– | 80ms 20ms | ▲ 0.05 Mbps ▼ 0.1 Mbps |
| Lossy-WiFi | 5ms | 3ms | pareto | 3% | 1% gap 5 | 5ms 2ms | 20 Mbps |
| Starlink | 20ms | ▲ 10ms ▼ 5ms |
normal | ▲ 1% ▼ 0.5% |
0.5% | – | ▲ 20 Mbps ▼ 100 Mbps |
| Satellite | 300ms | ▲ 50ms ▼ 30ms |
normal | ▲ 2.5% ▼ 1.5% |
– | – | ▲ 1 Mbps ▼ 5 Mbps |
| DSL | 15ms | 3ms | normal | 0.2% | – | – | ▲ 3 Mbps ▼ 25 Mbps |
| Cable | 5ms | 1ms | normal | 0.05% | – | – | ▲ 20 Mbps ▼ 200 Mbps |
| Airplane-WiFi | 150ms | ▲ 50ms ▼ 30ms |
pareto | ▲ 5% ▼ 3% |
1% gap 5 | 30ms 10ms | ▲ 1 Mbps ▼ 2 Mbps |
| Congested | 50ms | 40ms | paretonormal | 5% | 2% gap 3 | – | ▲ 0.5 Mbps ▼ 1 Mbps |
| Bursty | 10ms | 2ms | – | gemodel (burst) | – | – | 50 Mbps |
| ECN-Datacenter | 1ms | 0.5ms | normal | 2% ecn | – | – | 1 Gbps |
| ECN-WAN | 25ms | 5ms | normal | 0.5% ecn | – | – | ▲ 50 Mbps ▼ 100 Mbps |
Built-in profiles are defined in code, not written to the config file.
Configuration is stored in ~/.config/lagsim.yaml:
interfaces:
lan: eth0 # LAN-facing interface (auto-detected on first run)
ifb: ifb0 # IFB device (created automatically)
subnet: 192.168.1.0/24
root_rate: 1gbit
profiles:
# Override a built-in profile
3G:
delay: 100ms
jitter: 30ms
correlation: 25%
loss: 1.5%
rate: 2mbit
upload:
rate: 0.5mbit
# Add a custom profile
My-VPN:
delay: 30ms
jitter: 5ms
loss: 0.1%
rate: 50mbit
# Disable a built-in profile
Edge-2G: null
assignments:
192.168.1.100: 3G
192.168.1.101: LTE
names:
aa:bb:cc:dd:ee:f0: Living Room TV
aa:bb:cc:dd:ee:f1: Dad's PhoneOnly profiles that differ from the built-in defaults are saved to the config file. You can:
- Add custom profiles alongside the built-ins
- Override a built-in by redefining it (full replacement, not merged)
- Disable a built-in by setting it to
null
| Parameter | Description | Example |
|---|---|---|
delay |
Base latency added to each packet | 100ms |
jitter |
Random variation added to delay | 30ms |
correlation |
How much each packet's delay correlates with the previous | 25% |
distribution |
Jitter distribution: normal, pareto, or paretonormal |
paretonormal |
loss |
Packet loss — random or bursty (see below) | 1.5% |
ecn |
Mark packets with ECN CE bit instead of dropping (see below) | true |
duplicate |
Packet duplication probability | 0.5% |
reorder |
Packet reordering — random or with gap (see below) | 1% or 1% gap 5 |
corrupt |
Packet corruption probability | 0.1% |
rate |
Bandwidth limit | 2mbit |
slot |
Packet batching interval — holds then releases in bursts | 20ms 5ms |
All parameters are optional except delay. Values use tc/netem syntax.
Without a distribution, jitter is uniformly random. Setting distribution shapes how jitter values are picked:
normal— bell curve around the base delay. Good for stable links (DSL, cable, satellite) where variation is symmetric.pareto— heavy-tailed: most packets are near the base delay, but occasional packets get much larger spikes. Good for WiFi and other interference-prone links.paretonormal— blend of both: normal most of the time with pareto-like tail spikes. Good for cellular networks where handoffs and contention cause intermittent latency bursts.
The loss field supports netem's Gilbert-Elliott model for realistic bursty loss patterns — periods of clean transmission interrupted by short bursts of heavy packet loss:
loss: "gemodel p r 1-h 1-k"| Parameter | Meaning |
|---|---|
p |
Probability of entering the bad (lossy) state |
r |
Probability of returning to the good state |
1-h |
Loss rate in the bad state (e.g., 100% = total blackout) |
1-k |
Loss rate in the good state (e.g., 0% = no baseline loss) |
Example: loss: "gemodel 0.5% 15% 100% 0%" — clean most of the time, with occasional short bursts (~7 packets) of 100% loss. This models WiFi interference, cellular handoffs, or buffer overflows.
When ecn: true is set alongside loss, packets are marked with the ECN CE (Congestion Experienced) bit instead of being dropped. The packet still arrives, but ECN-aware TCP stacks treat it as a congestion signal and slow down. Useful for testing DCTCP, BBR, or QUIC ECN behavior:
profiles:
My-ECN-Test:
delay: 5ms
loss: 1%
ecn: true
rate: 1gbitBy default, reorder randomly reorders packets. Adding gap N makes it deterministic: every Nth packet is reordered with the given probability. This is more realistic for triggering TCP fast-retransmit (which fires after 3 duplicate ACKs):
reorder: "1% gap 5" # every 5th packet has a 1% chance of being reorderedThe slot parameter batches packets into time slots instead of sending them individually. Packets are held and released in bursts, simulating WiFi TDMA scheduling or cellular resource allocation:
slot: "20ms 5ms" # release a batch every 20ms ± 5ms jitterThis is especially noticeable for interactive traffic (VoIP, gaming) where micro-bursts affect perceived quality even when average throughput is fine.
Base parameters apply to both directions. Add download and/or upload sections to override specific parameters per direction — only the fields you specify are overridden, the rest inherit from the base:
profiles:
My-Satellite:
delay: 300ms
jitter: 30ms
loss: 1.5%
rate: 5mbit
upload:
rate: 1mbit # slower upload
jitter: 50ms # more jitter on uplink
loss: 2.5% # more loss on uplink
download:
rate: 10mbit # faster downloadCustom names are keyed by MAC address so they follow the device across IP changes. Edit names in the TUI with e, or set them directly in the config under names.
Assignments persist across reboots. Run lagsim init at startup to restore them (e.g. via systemd or cron @reboot):
# crontab -e
@reboot /usr/local/bin/lagsim initA practical way to use lagsim is to set up a dedicated Linux box as a router between a lab VLAN and the rest of your network. All devices on the lab VLAN — phones, tablets, TVs, IoT devices — get their traffic conditioned without any client-side configuration. Traffic between lab devices and anything on the other side (dev workstations, servers, the internet) goes through lagsim.
┌──────────┐ ┌──────────┐ ┌──────────┐
│ dev │ │ dev │ │ internet │
│workstat. │ │ server │ │ gateway │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
─────┴─────────────┴─────────────┴──── office LAN
│
│ eth0 (or eth0.100 VLAN tag)
┌──────┴──────┐
│ lagsim │
│ linux box │
└──────┬──────┘
│ eth1 (or eth0.200 VLAN tag)
│
┌────────────┼───────────┐
│ │ │
┌────┴────┐ ┌─────┴─────┐ ┌───┴───┐
│ phone │ │ tablet │ │ TV │
└─────────┘ └───────────┘ └───────┘
Lab WiFi (dedicated SSID)
The Linux box can use two physical interfaces (e.g., eth0 for the office LAN, eth1 for the lab) or a single interface with VLAN tagging (e.g., eth0.100 and eth0.200).
- Create a lab VLAN on your switch and assign a dedicated WiFi SSID to it
- Configure the Linux box with either two interfaces or VLAN sub-interfaces — one on the lab VLAN, one on the office LAN
- Enable IP forwarding so the box routes traffic between the two networks
- Run lagsim on the lab-facing interface:
sudo sysctl -w net.ipv4.ip_forward=1
sudo lagsimlagsim auto-detects the lab interface and discovers devices via ARP. You can then assign different profiles to different devices — for example, put a phone on "3G" and a TV on "Satellite" simultaneously.
This lets you test how your client/server application behaves under realistic network conditions: the clients are real devices on the lab VLAN, and the servers run on your workstation or dev servers on the office LAN — all traffic between them passes through lagsim.
- Linux with
tc,ip, and theifbkernel module - Root privileges
- Go 1.24+ to build
MIT