Bug Description
The DNSCaching option has two data races that are triggered under concurrent load (i.e., normal vegeta operation):
1. Shared DNS cache slice mutated in-place
resolver.LookupHost() returns a direct reference to the cached []string slice. The DialContext closure then mutates this shared slice via:
rng.Shuffle(len(ips), ...) -- reorders elements in-place
firstOfEachIPFamily(ips) -- uses the ips[:0] append trick, overwriting the backing array
When multiple goroutines resolve the same host concurrently, they all get the same slice and race on its elements. This can corrupt the DNS cache entries, leading to connections to wrong IPs.
2. Unsafe rand.Rand shared across goroutines
A private rand.New(rand.NewSource(...)) is created once and used inside the DialContext closure. rand.Rand is explicitly documented as not safe for concurrent use. Under concurrent dial attempts, this is a data race.
Reproduction
func TestDNSCaching_ConcurrentDialRace(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
defer srv.Close()
u, _ := url.Parse(srv.URL)
target := "http://localhost:" + u.Port()
atk := NewAttacker(DNSCaching(0))
tr := NewStaticTargeter(Target{Method: "GET", URL: target})
a := &attack{name: "dns-race-test", began: time.Now()}
var wg sync.WaitGroup
for i := 0; i < 20; i++ {
wg.Add(1)
go func() {
defer wg.Done()
atk.hit(tr, a)
}()
}
wg.Wait()
}
Running with go test -race triggers:
WARNING: DATA RACE
Read at ... firstOfEachIPFamily() attack.go:419
Previous write at ... firstOfEachIPFamily() attack.go:426
Fix
- Copy the
ips slice before mutating it
- Use the global
rand.Shuffle (concurrency-safe) instead of a private rand.Rand
PR forthcoming.
Bug Description
The
DNSCachingoption has two data races that are triggered under concurrent load (i.e., normal vegeta operation):1. Shared DNS cache slice mutated in-place
resolver.LookupHost()returns a direct reference to the cached[]stringslice. TheDialContextclosure then mutates this shared slice via:rng.Shuffle(len(ips), ...)-- reorders elements in-placefirstOfEachIPFamily(ips)-- uses theips[:0]append trick, overwriting the backing arrayWhen multiple goroutines resolve the same host concurrently, they all get the same slice and race on its elements. This can corrupt the DNS cache entries, leading to connections to wrong IPs.
2. Unsafe
rand.Randshared across goroutinesA private
rand.New(rand.NewSource(...))is created once and used inside theDialContextclosure.rand.Randis explicitly documented as not safe for concurrent use. Under concurrent dial attempts, this is a data race.Reproduction
Running with
go test -racetriggers:Fix
ipsslice before mutating itrand.Shuffle(concurrency-safe) instead of a privaterand.RandPR forthcoming.