#752·vegeta

Data race in ConnectTo round-robin address selection

Author: queeliusCreated Mar 25, 2026Updated Mar 25, 2026

Bug Description

The ConnectTo option creates a DialContext closure that reads and writes a round-robin counter (cm.n) without any synchronization:

go
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
    if cm, ok := connectTo[addr]; ok {
        cm.n = (cm.n + 1) % len(cm.addrs)  // unsynchronized read-modify-write
        addr = cm.addrs[cm.n]
    }
    return dial(ctx, network, addr)
}

DialContext is called concurrently by Go's HTTP transport, so concurrent requests to the same target host will race on cm.n. This can corrupt the counter value and potentially index out of bounds.

Reproduction

Running this with -race reliably triggers the detector:

go
func TestConnectTo_ConcurrentDialRace(t *testing.T) {
    // Set up multiple backend servers, create attacker with ConnectTo,
    // then fire 20 concurrent hits
    var wg sync.WaitGroup
    for i := 0; i < 20; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            atk.hit(tr, a)
        }()
    }
    wg.Wait()
}

Output:

WARNING: DATA RACE
Read at 0x00c00007e0b8 by goroutine 34:
  ...ConnectTo.func4.1()
      vegeta/lib/attack.go:309
Previous write at 0x00c00007e0b8 by goroutine 33:
  ...ConnectTo.func4.1()
      vegeta/lib/attack.go:309

Fix

Add a sync.Mutex per round-robin group and lock around the counter update.

PR forthcoming.