On macOS, the most practical way to simulate these conditions is to combine Apple's Network Link Conditioner (or pfctl/dnctl on older systems) with a tool that can periodically interrupt connectivity. Since you mentioned Go, you can also build the network impairment directly into your application's HTTP transport if you only want to affect your Go application rather than the entire machine.
Option 1: Simulate the entire machine (recommended for end-to-end testing)
Install Network Link Conditioner
Apple includes Network Link Conditioner as part of the Additional Tools for Xcode.
It allows you to configure:
- Bandwidth
- Latency
- Packet loss
Your target settings would be:
| Setting | Value |
|---|
| Downlink | 40 Kbps |
| Uplink | 40 Kbps |
| Delay | 1000 ms |
| Packet Loss | 10% |
Unfortunately it does not support
- random jitter
- periodic disconnects
so you'll need additional tooling.
Add jitter
The modern macOS networking tools are much more limited than Linux's tc netem.
A common solution is to run your Go service inside Docker and use Linux tc netem:
tc qdisc add dev eth0 root netem \
delay 1000ms 2000ms distribution normal \
loss 10% \
rate 40kbit
This gives
- 40 kbps
- 1000 ms base latency
- ±2000 ms jitter
- 10% packet loss
which is significantly more realistic than what macOS provides.
Simulate periodic 15-second outages
You can temporarily disable the network interface.
For Wi-Fi:
networksetup -setairportpower en0 off
sleep 15
networksetup -setairportpower en0 on
Repeat every few minutes:
while true; do
sleep 180
networksetup -setairportpower en0 off
sleep 15
networksetup -setairportpower en0 on
done
If you're using Ethernet, you can instead disable that interface with ifconfig.
Option 2: Simulate inside your Go application
If you're only testing your own application, this is often much easier.
Create a custom RoundTripper that:
- sleeps 1 second before every request
- adds random jitter
- randomly drops 10% of requests
- limits bandwidth to 40 kbps
- periodically blocks for 15 seconds
Example skeleton:
type ChaosTransport struct {
Base http.RoundTripper
LastDropout time.Time
mu sync.Mutex
}
func (c *ChaosTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// Base latency
time.Sleep(time.Second)
// Jitter
jitter := time.Duration(rand.Intn(2000)) * time.Millisecond
time.Sleep(jitter)
// Packet loss
if rand.Float64() < 0.10 {
return nil, io.ErrUnexpectedEOF
}
// Periodic outage
c.mu.Lock()
if time.Since(c.LastDropout) > 3*time.Minute {
c.LastDropout = time.Now()
c.mu.Unlock()
time.Sleep(15 * time.Second)
return nil, context.DeadlineExceeded
}
c.mu.Unlock()
return c.Base.RoundTrip(req)
}
Use it like:
client := &http.Client{
Transport: &ChaosTransport{
Base: http.DefaultTransport,
},
}
Simulating 40 kbps in Go
Wrap the response body in a rate-limited reader.
Using golang.org/x/time/rate:
limiter := rate.NewLimiter(rate.Limit(5000), 5000)
40 kbps ≈ 5 KB/s.
Each read waits on the limiter before returning data.
Option 3: Toxiproxy (highly recommended)
Many Go developers use Toxiproxy for this exact purpose.
It supports:
- latency
- jitter
- bandwidth limits
- packet loss
- enabling/disabling a connection
You can also toggle a proxy off for exactly 15 seconds using its API.
This approach is deterministic, scriptable, works well in CI, and doesn't affect the rest of your machine.
Recommended setup
For a Go application on macOS, a robust setup is:
- Use Network Link Conditioner (or Docker +
tc netem) to enforce:
- 40 kbps bandwidth
- 1000 ms latency
- 10% packet loss
- Use Toxiproxy in front of the service your application talks to:
- Add ±2000 ms jitter.
- Programmatically disable the proxy for 15 seconds every few minutes to simulate complete connectivity loss.
- If you only need to test your own client code (rather than the whole system), implement a custom
http.RoundTripper that injects latency, jitter, packet loss, bandwidth throttling, and periodic outages directly into the application's network stack. This keeps the simulation self-contained and easy to automate in local development and integration tests.