Skip to content

Commit

Permalink
[internal/common/testutil] Add support for testing listening udp ports (
Browse files Browse the repository at this point in the history
#12888)

* Add support for testing listening udp ports
  • Loading branch information
jspaleta authored Aug 2, 2022
1 parent 1a445fe commit d00fbea
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 11 deletions.
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

0 comments on commit d00fbea

Please sign in to comment.