From 268f11e4fcde1604b8593e6bc35eb6e138699cb5 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Wed, 9 Dec 2020 16:44:54 +0100 Subject: [PATCH] agent: allow to set SO_REUSEPORT on listening socket Introduce Option.SocketReuseAddrAndPort which, if set, will lead to the SO_REUSEPORT socket option being set on the listening socket on Unix-like OSes. This also sets SO_REUSEADDR which is already the default in net.Listen (see net.setDefaultSockopts). Setting these options increases the chance to re-bind() to the same address and port upon agent restart if Options.Addr is set. Signed-off-by: Tobias Klauser --- agent/agent.go | 15 +++++++++++++-- agent/agent_test.go | 11 +++++++++++ agent/sockopt_unix.go | 36 ++++++++++++++++++++++++++++++++++++ agent/sockopt_unsupported.go | 13 +++++++++++++ go.mod | 2 +- go.sum | 4 ++-- 6 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 agent/sockopt_unix.go create mode 100644 agent/sockopt_unsupported.go diff --git a/agent/agent.go b/agent/agent.go index 59a03cf..441814c 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -8,6 +8,7 @@ package agent import ( "bufio" + "context" "encoding/binary" "fmt" "io" @@ -55,6 +56,13 @@ type Options struct { // can call Close before shutting down. // Optional. ShutdownCleanup bool + + // ReuseSocketAddrAndPort determines whether the SO_REUSEADDR and + // SO_REUSEADDR socket options should be set on the listening socket of + // the agent. This option is only effective on unix-like OSes and if + // Addr is set to a fixed host:port. + // Optional. + ReuseSocketAddrAndPort bool } // Listen starts the gops agent on a host process. Once agent started, users @@ -96,11 +104,14 @@ func Listen(opts Options) error { if addr == "" { addr = defaultAddr } - ln, err := net.Listen("tcp", addr) + var lc net.ListenConfig + if opts.ReuseSocketAddrAndPort { + lc.Control = setsockoptReuseAddrAndPort + } + listener, err = lc.Listen(context.Background(), "tcp", addr) if err != nil { return err } - listener = ln port := listener.Addr().(*net.TCPAddr).Port portfile = fmt.Sprintf("%s/%d", gopsdir, os.Getpid()) err = ioutil.WriteFile(portfile, []byte(strconv.Itoa(port)), os.ModePerm) diff --git a/agent/agent_test.go b/agent/agent_test.go index fab74e6..e335a56 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -54,6 +54,17 @@ func TestAgentListenMultipleClose(t *testing.T) { Close() } +func TestAgentListenReuseAddrAndPort(t *testing.T) { + err := Listen(Options{ + Addr: "127.0.0.1:50000", + ReuseSocketAddrAndPort: true, + }) + if err != nil { + t.Fatal(err) + } + Close() +} + func TestFormatBytes(t *testing.T) { tests := []struct { val uint64 diff --git a/agent/sockopt_unix.go b/agent/sockopt_unix.go new file mode 100644 index 0000000..7edaf20 --- /dev/null +++ b/agent/sockopt_unix.go @@ -0,0 +1,36 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !js,!plan9,!windows + +package agent + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +// setsockoptReuseAddrAndPort sets the SO_REUSEADDR and SO_REUSEPORT socket +// options on c's underlying socket in order to increase the chance to re-bind() +// to the same address and port upon agent restart. +func setsockoptReuseAddrAndPort(network, address string, c syscall.RawConn) error { + var soerr error + if err := c.Control(func(su uintptr) { + sock := int(su) + // Allow reuse of recently-used addresses. This socket option is + // set by default on listeners in Go's net package, see + // net.setDefaultSockopts. + soerr = unix.SetsockoptInt(sock, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) + if soerr != nil { + return + } + // Allow reuse of recently-used ports. This gives the agent a + // better chance to re-bind upon restarts. + soerr = unix.SetsockoptInt(sock, unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + }); err != nil { + return err + } + return soerr +} diff --git a/agent/sockopt_unsupported.go b/agent/sockopt_unsupported.go new file mode 100644 index 0000000..df3223b --- /dev/null +++ b/agent/sockopt_unsupported.go @@ -0,0 +1,13 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build js,wasm plan9 windows + +package agent + +import "syscall" + +func setsockoptReuseAddrAndPort(network, address string, c syscall.RawConn) error { + return nil +} diff --git a/go.mod b/go.mod index eda4331..a62188b 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,6 @@ require ( github.com/shirou/gopsutil v2.20.4+incompatible github.com/stretchr/testify v1.3.0 // indirect github.com/xlab/treeprint v1.0.0 - golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 // indirect + golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d rsc.io/goversion v1.2.0 ) diff --git a/go.sum b/go.sum index 0a4c5e6..959a2e3 100644 --- a/go.sum +++ b/go.sum @@ -15,7 +15,7 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/xlab/treeprint v1.0.0 h1:J0TkWtiuYgtdlrkkrDLISYBQ92M+X5m4LrIIMKrbDTs= github.com/xlab/treeprint v1.0.0/go.mod h1:IoImgRak9i3zJyuxOKUP1v4UZd1tMoKkq/Cimt1uhCg= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 h1:YTzHMGlqJu67/uEo1lBv0n3wBXhXNeUbB1XfN2vmTm0= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d h1:MiWWjyhUzZ+jvhZvloX6ZrUsdEghn8a64Upd8EMHglE= +golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w= rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=