diff --git a/README.md b/README.md index 4a952147..284fffc5 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,8 @@ attack command: Number of redirects to follow. -1 will not follow but marks as success (default 10) -resolvers value List of addresses (ip:port) to use for DNS resolution. Disables use of local system DNS. (comma separated list) + -reuseaddr + Set the SO_REUSEADDR socket option (default false) -root-certs value TLS root certificate files (comma separated list) -session-tickets @@ -392,6 +394,12 @@ the response is marked as successful. Specifies custom DNS resolver addresses to use for name resolution instead of the ones configured by the operating system. Works only on non Windows systems. +#### `-resolvers` + +Specifies whether to set the SO_REUSEADDR option on the TCP socket before binding +it. Can reduce the amount of "bind: address already in use" errors when doing many +connections to the same server and port combination. + #### `-root-certs` Specifies the trusted TLS root CAs certificate files as a comma separated diff --git a/attack.go b/attack.go index f83c2cba..7354133d 100644 --- a/attack.go +++ b/attack.go @@ -62,6 +62,7 @@ func attackCmd() command { fs.StringVar(&opts.promAddr, "prometheus-addr", "", "Prometheus exporter listen address [empty = disabled]. Example: 0.0.0.0:8880") fs.Var(&dnsTTLFlag{&opts.dnsTTL}, "dns-ttl", "Cache DNS lookups for the given duration [-1 = disabled, 0 = forever]") fs.BoolVar(&opts.sessionTickets, "session-tickets", false, "Enable TLS session resumption using session tickets") + fs.BoolVar(&opts.reuseaddr, "reuseaddr", false, "Set the SO_REUSEADDR socket option (default false)") systemSpecificFlags(fs, opts) return command{fs, func(args []string) error { @@ -108,6 +109,7 @@ type attackOpts struct { promAddr string dnsTTL time.Duration sessionTickets bool + reuseaddr bool } // attack validates the attack arguments, sets up the @@ -219,6 +221,7 @@ func attack(opts *attackOpts) (err error) { vegeta.ChunkedBody(opts.chunked), vegeta.DNSCaching(opts.dnsTTL), vegeta.SessionTickets(opts.sessionTickets), + vegeta.Reuseaddr(opts.reuseaddr), ) res := atk.Attack(tr, opts.rate, opts.duration, opts.name) diff --git a/lib/attack.go b/lib/attack.go index caebba6f..7d131064 100644 --- a/lib/attack.go +++ b/lib/attack.go @@ -14,6 +14,7 @@ import ( "strconv" "sync" "time" + "syscall" "github.com/rs/dnscache" "golang.org/x/net/http2" @@ -134,6 +135,24 @@ func ChunkedBody(b bool) func(*Attacker) { return func(a *Attacker) { a.chunked = b } } +// Reuseaddr returns a functional option which makes the attacker set the +// SO_REUSEADDR option on the socket before binding it. +func Reuseaddr(b bool) func(*Attacker) { + return func(a *Attacker) { + if b { + a.dialer.Control = func(network, address string, conn syscall.RawConn) error { + var syserr error + if err := conn.Control(func(fd uintptr) { + syserr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + }); err != nil { + return err + } + return syserr + } + } + } +} + // Redirects returns a functional option which sets the maximum // number of redirects an Attacker will follow. func Redirects(n int) func(*Attacker) {