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

[internal/common/testutil] Add support for testing listening udp ports #12888

Merged
merged 2 commits into from
Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 35 additions & 11 deletions internal/common/testutil/testutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,34 @@ type portpair struct {
last string
}

// GetAvailableLocalAddress finds an available local port and returns an endpoint
// GetAvailableLocalAddress finds an available local port on tcp network and returns an endpoint
// describing it. The port is available for opening when this function returns
// provided that there is no race by some other code to grab the same port
// immediately.
func GetAvailableLocalAddress(t testing.TB) string {
return GetAvailableLocalNetworkAddress(t, "tcp")
}

// GetAvailableLocalNetworkAddress finds an available local port on specified network and returns an endpoint
// describing it. The port is available for opening when this function returns
// provided that there is no race by some other code to grab the same port
// immediately.
func GetAvailableLocalNetworkAddress(t testing.TB, network string) string {
// Retry has been added for windows as net.Listen can return a port that is not actually available. Details can be
// found in https://github.com/docker/for-win/issues/3171 but to summarize Hyper-V will reserve ranges of ports
// which do not show up under the "netstat -ano" but can only be found by
// "netsh interface ipv4 show excludedportrange protocol=tcp". We'll use []exclusions to hold those ranges and
// retry if the port returned by GetAvailableLocalAddress falls in one of those them.
var exclusions []portpair

portFound := false
if runtime.GOOS == "windows" {
exclusions = getExclusionsList(t)
}

var endpoint string
for !portFound {
endpoint = findAvailableAddress(t)
endpoint = findAvailableAddress(t, network)
_, port, err := net.SplitHostPort(endpoint)
require.NoError(t, err)
portFound = true
Expand All @@ -66,15 +75,30 @@ func GetAvailableLocalAddress(t testing.TB) string {
return endpoint
}

func findAvailableAddress(t testing.TB) string {
ln, err := net.Listen("tcp", "localhost:0")
require.NoError(t, err, "Failed to get a free local port")
// There is a possible race if something else takes this same port before
// the test uses it, however, that is unlikely in practice.
defer func() {
assert.NoError(t, ln.Close())
}()
return ln.Addr().String()
func findAvailableAddress(t testing.TB, network string) string {
switch network {
// net.Listen supported network strings
case "tcp", "tcp4", "tcp6", "unix", "unixpacket":
ln, err := net.Listen(network, "localhost:0")
require.NoError(t, err, "Failed to get a free local port")
// There is a possible race if something else takes this same port before
// the test uses it, however, that is unlikely in practice.
defer func() {
assert.NoError(t, ln.Close())
}()
return ln.Addr().String()
// net.ListenPacket supported network strings
case "udp", "udp4", "udp6", "unixgram":
ln, err := net.ListenPacket(network, "localhost:0")
require.NoError(t, err, "Failed to get a free local port")
// There is a possible race if something else takes this same port before
// the test uses it, however, that is unlikely in practice.
defer func() {
assert.NoError(t, ln.Close())
}()
return ln.LocalAddr().String()
}
return ""
}

// Get excluded ports on Windows from the command: netsh interface ipv4 show excludedportrange protocol=tcp
Expand Down
15 changes: 15 additions & 0 deletions internal/common/testutil/testutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ func TestGetAvailableLocalAddress(t *testing.T) {
require.Error(t, err)
require.Nil(t, ln1)
}
func TestGetAvailableLocalUDPAddress(t *testing.T) {
addr := GetAvailableLocalNetworkAddress(t, "udp")
// Endpoint should be free.
ln0, err := net.ListenPacket("udp", addr)
require.NoError(t, err)
require.NotNil(t, ln0)
t.Cleanup(func() {
require.NoError(t, ln0.Close())
})

// Ensure that the endpoint wasn't something like ":0" by checking that a second listener will fail.
ln1, err := net.ListenPacket("udp", addr)
require.Error(t, err)
require.Nil(t, ln1)
}

func TestCreateExclusionsList(t *testing.T) {
// Test two examples of typical output from "netsh interface ipv4 show excludedportrange protocol=tcp"
Expand Down