Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tune http.Client's Transport's MaxIdleConns/MaxIdleConnsPerHost #127

Closed
0xdevalias opened this issue Feb 23, 2019 · 4 comments
Closed

Tune http.Client's Transport's MaxIdleConns/MaxIdleConnsPerHost #127

0xdevalias opened this issue Feb 23, 2019 · 4 comments

Comments

@0xdevalias
Copy link
Contributor

0xdevalias commented Feb 23, 2019

In doing some work on another project, i've learnt far more than I ever expected to about go's http.Client, and how it's defaults may not be ideal. We're already setting timeouts, which is good, but the defaults around idle connection reuse are pretty woeful:

This is our current settings:

Some refs:

Looking at the source for net/http/transport.go we can see that the MaxIdleConnections is 100, but DefaultMaxIdleConnsPerHost is only 2:

var DefaultTransport RoundTripper = &Transport{
	Proxy: ProxyFromEnvironment,
	DialContext: (&net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
		DualStack: true,
	}).DialContext,
	MaxIdleConns:          100,
	IdleConnTimeout:       90 * time.Second,
	TLSHandshakeTimeout:   10 * time.Second,
	ExpectContinueTimeout: 1 * time.Second,
}

// DefaultMaxIdleConnsPerHost is the default value of Transport's
// MaxIdleConnsPerHost.
const DefaultMaxIdleConnsPerHost = 2

What this means in practical terms is that for every 100 connections we open to a webserver, we close 98 of them, which leads to a lot of sockets left in our kernel in the TIME_WAITstate, and also means we need to pay the overhead of establishing a new TCP (and/or TLS) handshake on those 98 closed sockets.

I'm not 100% on this, but I think the best setting would be to set MaxIdleConns == MaxIdleConnsPerHost == number of goroutines/'threads' specified.

Then if we make sure that we set keep-alive on requests to the server, if supported, it should mean that we get a lot more reuse per socket, which should mean faster gobusting.

For this to work, you also have to ensure you fully read/close the response body:

Just be sure to fully read (and/or close) the Response.Body, otherwise that TCP connection won't be re-used.

It looks as though the entire body is already being read, in these areas, so this should be fine:

There are a LOT more little things that can be tuned in the http.Client's Transport, but not sure if/what would make as much difference as this.

Making use of keep-alive was one of the techniques discussed in this research:

If anyone wants to benchmark the speed/resource difference before/after, i'd be pretty interested to see just how much of an improvement it makes, but from the above video, it claims about a 400% increase from reusing the connections (and like 6000% if you want to use http pipelining..)

@OJ
Copy link
Owner

OJ commented Mar 22, 2019

I'll try to get to this very soon. Nice research @0xdevalias thank you!

@OJ OJ added the enhancement label Mar 22, 2019
@iSteed
Copy link

iSteed commented Apr 17, 2019

came here to ask about this, i am looking for a simple way to control the amount of connections i am establishing via the command line.

@firefart firefart added this to the v3 milestone Apr 17, 2019
@firefart
Copy link
Collaborator

Did some quick testing on windows and there is indeed a speedup.

Default values from the optimization branch:

PS C:\Users\firefart\Documents\code\gobuster> Measure-Command { .\v3.exe dir -u https://firefart.at -w .\wordlist_small.txt }
2019/04/17 21:12:23 Starting gobuster
2019/04/17 21:12:39 Finished%)


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 16
Milliseconds      : 702
Ticks             : 167029543
TotalDays         : 0,000193321230324074
TotalHours        : 0,00463970952777778
TotalMinutes      : 0,278382571666667
TotalSeconds      : 16,7029543
TotalMilliseconds : 16702,9543



PS C:\Users\firefart\Documents\code\gobuster> Measure-Command { .\v3.exe dir -u https://firefart.at -w .\wordlist_small.txt }
2019/04/17 21:12:42 Starting gobuster
2019/04/17 21:12:59 Finished%)


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 16
Milliseconds      : 918
Ticks             : 169185885
TotalDays         : 0,000195816996527778
TotalHours        : 0,00469960791666667
TotalMinutes      : 0,281976475
TotalSeconds      : 16,9185885
TotalMilliseconds : 16918,5885

And after setting the following values:

	client.client = &http.Client{
		Timeout:       opt.Timeout,
		CheckRedirect: redirectFunc,
		Transport: &http.Transport{
			Proxy:               proxyURLFunc,
			MaxIdleConns:        100,
			MaxIdleConnsPerHost: 100,
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: opt.InsecureSSL,
			},
		}}
PS C:\Users\firefart\Documents\code\gobuster> Measure-Command { .\v3.exe dir -u https://firefart.at -w .\wordlist_small.txt }
2019/04/17 21:13:12 Starting gobuster
2019/04/17 21:13:27 Finished%)


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 14
Milliseconds      : 976
Ticks             : 149768399
TotalDays         : 0,000173343054398148
TotalHours        : 0,00416023330555556
TotalMinutes      : 0,249613998333333
TotalSeconds      : 14,9768399
TotalMilliseconds : 14976,8399



PS C:\Users\firefart\Documents\code\gobuster> Measure-Command { .\v3.exe dir -u https://firefart.at -w .\wordlist_small.txt }
2019/04/17 21:13:29 Starting gobuster
2019/04/17 21:13:44 Finished%)


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 14
Milliseconds      : 804
Ticks             : 148046087
TotalDays         : 0,000171349637731481
TotalHours        : 0,00411239130555556
TotalMinutes      : 0,246743478333333
TotalSeconds      : 14,8046087
TotalMilliseconds : 14804,6087

So with default parameters it takes around 17 seconds for a 5000 entry wordlist and with both parameters set to 100 we are around 15 seconds.

So I think we should use this optimization and hope we will not kill many servers with it :)

firefart added a commit that referenced this issue Apr 17, 2019
@firefart firefart mentioned this issue Apr 17, 2019
@firefart
Copy link
Collaborator

I created a PR over here:
#140

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants