Skip to content

Commit

Permalink
Refactor ip in use algorithm (#4268)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisdoherty4 authored Dec 7, 2022
1 parent 2412bef commit d796af8
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 21 deletions.
28 changes: 14 additions & 14 deletions pkg/networkutils/networkutils.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package networkutils

import (
"errors"
"fmt"
"net"
"strconv"
"syscall"
"time"
)

Expand All @@ -24,23 +26,21 @@ func ValidateIP(ip string) error {
return nil
}

// IsIPInUse performs a soft check to see if there are any services listening on a selection of common ports at
// ip by trying to establish a TCP connection. Ports checked include: 22, 23, 80, 443 and 6443 (Kubernetes API Server).
// Each connection attempt allows up-to 500ms for a response.
//
// todo(chrisdoherty) change to an icmp approach to eliminate the need for ports.
// IsIPInUse performs a best effort check to see if an IP address is in use. It is not completely
// reliable as testing if an IP is in use is inherently difficult, particularly with non-trivial
// network topologies.
func IsIPInUse(client NetClient, ip string) bool {
ports := []string{"22", "23", "80", "443", "6443"}
for _, port := range ports {
address := net.JoinHostPort(ip, port)
conn, err := client.DialTimeout("tcp", address, 500*time.Millisecond)
if err == nil {
conn.Close()
return true
}
// Dial and immediately close the connection if it was established as its superfluous for
// our check. We use port 80 as its common and is more likely to get through firewalls
// than other ports.
conn, err := client.DialTimeout("tcp", net.JoinHostPort(ip, "80"), 500*time.Millisecond)
if err == nil {
conn.Close()
}

return false
// If we establish a connection or we receive a response assume that address is in use.
// The latter case covers situations like an IP in use but the port requested is not open.
return err == nil || errors.Is(err, syscall.ECONNREFUSED) || errors.Is(err, syscall.ECONNRESET)
}

func IsPortInUse(client NetClient, host string, port string) bool {
Expand Down
14 changes: 13 additions & 1 deletion pkg/networkutils/networkutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"net"
"reflect"
"syscall"
"testing"
"time"

Expand Down Expand Up @@ -41,13 +42,24 @@ func TestIsIPInUsePass(t *testing.T) {

client := mocks.NewMockNetClient(ctrl)
client.EXPECT().DialTimeout(gomock.Any(), gomock.Any(), gomock.Any()).
Times(5).
Return(nil, errors.New("no connection"))

res := networkutils.IsIPInUse(client, "10.10.10.10")
g.Expect(res).To(gomega.BeFalse())
}

func TestIsIPInUseConnectionRefused(t *testing.T) {
ctrl := gomock.NewController(t)
g := gomega.NewWithT(t)

client := mocks.NewMockNetClient(ctrl)
client.EXPECT().DialTimeout(gomock.Any(), gomock.Any(), gomock.Any()).
Return(nil, syscall.ECONNREFUSED)

res := networkutils.IsIPInUse(client, "10.10.10.10")
g.Expect(res).To(gomega.BeTrue())
}

func TestIsIPInUseFail(t *testing.T) {
ctrl := gomock.NewController(t)
g := gomega.NewWithT(t)
Expand Down
2 changes: 0 additions & 2 deletions pkg/providers/tinkerbell/assert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ func TestNewIPNotInUseAssertion_NotInUseSucceeds(t *testing.T) {
netClient := mocks.NewMockNetClient(ctrl)
netClient.EXPECT().
DialTimeout(gomock.Any(), gomock.Any(), gomock.Any()).
Times(5).
Return(nil, errors.New("failed to connect"))

clusterSpec := NewDefaultValidClusterSpecBuilder().Build()
Expand Down Expand Up @@ -187,7 +186,6 @@ func TestAssertTinkerbellIPNotInUse_NotInUseSucceeds(t *testing.T) {
netClient := mocks.NewMockNetClient(ctrl)
netClient.EXPECT().
DialTimeout(gomock.Any(), gomock.Any(), gomock.Any()).
Times(5).
Return(nil, errors.New("failed to connect"))

clusterSpec := NewDefaultValidClusterSpecBuilder().Build()
Expand Down
4 changes: 2 additions & 2 deletions pkg/providers/tinkerbell/tinkerbell.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ import (
)

const (
maxRetries = 30
backOffPeriod = 5 * time.Second
maxRetries = 30
backOffPeriod = 5 * time.Second
)

var (
Expand Down
3 changes: 1 addition & 2 deletions pkg/providers/validator/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"github.com/aws/eks-anywhere/pkg/providers/validator"
)

func TestValidateControlPlaneIpUniqueness(t *testing.T) {
func TestValidateControlPlaneIPUniqueness(t *testing.T) {
g := NewWithT(t)
cluster := &v1alpha1.Cluster{
Spec: v1alpha1.ClusterSpec{
Expand All @@ -30,7 +30,6 @@ func TestValidateControlPlaneIpUniqueness(t *testing.T) {
ctrl := gomock.NewController(t)
client := mocks.NewMockNetClient(ctrl)
client.EXPECT().DialTimeout(gomock.Any(), gomock.Any(), gomock.Any()).
Times(5).
Return(nil, errors.New("no connection"))
ipValidator := validator.NewIPValidator(validator.CustomNetClient(client))

Expand Down

0 comments on commit d796af8

Please sign in to comment.