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

Validate RLIMIT_NOFILE against limits.http_max_conns_per_client #7434

Merged
20 changes: 20 additions & 0 deletions agent/config/agent_limits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package config

import (
"fmt"
)

// checkLimitsFromMaxConnsPerClient check that value provided might be OK
// return an error if values are not compatible
func checkLimitsFromMaxConnsPerClient(maxConnsPerClient int) error {
maxFds, err := getrlimit()
if err == nil && maxConnsPerClient > 0 {
// We need the list port + a few at the minimum
// On Mac OS, 20 FDs are open by Consul without doing anything
requiredFds := uint64(maxConnsPerClient + 20)
if maxFds < requiredFds {
return fmt.Errorf("system allows a max of %d file descriptors, but limits.http_max_conns_per_client: %d needs at least %d", maxFds, maxConnsPerClient, requiredFds)
}
}
return err
}
43 changes: 43 additions & 0 deletions agent/config/agent_limits_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package config

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestBuildAndValidate_HTTPMaxConnsPerClientExceedsRLimit(t *testing.T) {
t.Parallel()
hcl := `
limits{
# We put a very high value to be sure to fail
# This value is more than max on Windows as well
http_max_conns_per_client = 16777217
}`
b, err := NewBuilder(Flags{})
assert.NoError(t, err)
testsrc := Source{
Name: "test",
Format: "hcl",
Data: `
ae_interval = "1m"
data_dir="/tmp/00000000001979"
bind_addr = "127.0.0.1"
advertise_addr = "127.0.0.1"
datacenter = "dc1"
bootstrap = true
server = true
node_id = "00000000001979"
node_name = "Node-00000000001979"
`,
}
b.Head = append(b.Head, testsrc)
b.Tail = append(b.Tail, DefaultConsulSource(), DevConsulSource())
b.Tail = append(b.Head, Source{Name: "hcl", Format: "hcl", Data: hcl})

_, validationError := b.BuildAndValidate()
if validationError == nil {
assert.Fail(t, "Config should not be valid")
}
assert.Contains(t, validationError.Error(), "but limits.http_max_conns_per_client: 16777217 needs at least 16777237")
}
4 changes: 4 additions & 0 deletions agent/config/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,10 @@ func (b *Builder) Validate(rt RuntimeConfig) error {
}
}

if err := checkLimitsFromMaxConnsPerClient(rt.HTTPMaxConnsPerClient); err != nil {
return err
}

return nil
}

Expand Down
13 changes: 13 additions & 0 deletions agent/config/limits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// +build !windows

package config

import "golang.org/x/sys/unix"

// getrlimit return the max file descriptors allocated by system
// return the number of file descriptors max
func getrlimit() (uint64, error) {
var limit unix.Rlimit
err := unix.Getrlimit(unix.RLIMIT_NOFILE, &limit)
return uint64(limit.Cur), err
}
9 changes: 9 additions & 0 deletions agent/config/limits_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// +build windows

package config

// getrlimit is no-op on Windows, as max fd/process is 2^24 on Wow64
// Return (16 777 216, nil)
func getrlimit() (uint64, error) {
return 16_777_216, nil
}