mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 18:29:53 +00:00
212 lines
5.0 KiB
Go
212 lines
5.0 KiB
Go
package collect
|
|
|
|
import (
|
|
"bytes"
|
|
"net"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/replicatedhq/troubleshoot/pkg/debug"
|
|
"github.com/segmentio/ksuid"
|
|
validation "k8s.io/apimachinery/pkg/util/validation"
|
|
)
|
|
|
|
type NetworkStatus string
|
|
|
|
const (
|
|
NetworkStatusAddressInUse = "address-in-use"
|
|
NetworkStatusConnectionRefused = "connection-refused"
|
|
NetworkStatusConnectionTimeout = "connection-timeout"
|
|
NetworkStatusConnected = "connected"
|
|
NetworkStatusErrorOther = "error"
|
|
NetworkStatusBindPermissionDenied = "bind-permission-denied"
|
|
NetworkStatusInvalidAddress = "invalid-address"
|
|
)
|
|
|
|
type NetworkStatusResult struct {
|
|
Status NetworkStatus `json:"status"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
var ipRegexp = regexp.MustCompile(`^[0-9.]+$`)
|
|
|
|
func isValidLoadBalancerAddress(address string) bool {
|
|
splitString := strings.Split(address, ":")
|
|
|
|
if len(splitString) != 2 { // should be hostAddress:port
|
|
return false
|
|
}
|
|
hostAddress := splitString[0]
|
|
port, err := strconv.Atoi(splitString[1])
|
|
if err != nil {
|
|
return false
|
|
}
|
|
portErrors := validation.IsValidPortNum(port)
|
|
|
|
if len(portErrors) > 0 {
|
|
return false
|
|
}
|
|
|
|
// Checking for uppercase letters
|
|
if strings.ToLower(hostAddress) != hostAddress {
|
|
return false
|
|
}
|
|
|
|
// Checking if it's all numbers and .
|
|
if ipRegexp.MatchString(hostAddress) {
|
|
|
|
// Check for isValidIP
|
|
|
|
test := validation.IsValidIP(nil, hostAddress)
|
|
return len(test) == 0
|
|
|
|
}
|
|
|
|
errs := validation.IsQualifiedName(hostAddress)
|
|
|
|
return len(errs) == 0
|
|
}
|
|
|
|
func checkTCPConnection(progressChan chan<- interface{}, listenAddress string, dialAddress string, timeout time.Duration) (NetworkStatus, error) {
|
|
|
|
if !isValidLoadBalancerAddress(dialAddress) {
|
|
return NetworkStatusInvalidAddress, errors.Errorf("Invalid Load Balancer Address: %v", dialAddress)
|
|
}
|
|
|
|
lstn, err := net.Listen("tcp", listenAddress)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "address already in use") {
|
|
return NetworkStatusAddressInUse, nil
|
|
}
|
|
|
|
return NetworkStatusErrorOther, errors.Wrap(err, "failed to create listener")
|
|
}
|
|
defer lstn.Close()
|
|
|
|
// The server may receive requests from other clients and the client request may be forwarded to
|
|
// other servers. The client must continue to initiate new connections and send its request
|
|
// token until the server responds with its token.
|
|
requestToken := ksuid.New().Bytes()
|
|
responseToken := ksuid.New().Bytes()
|
|
go func() {
|
|
for {
|
|
conn, err := lstn.Accept()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if handleTestConnection(conn, requestToken, responseToken) {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
stopAfter := time.Now().Add(timeout)
|
|
|
|
for {
|
|
if time.Now().After(stopAfter) {
|
|
debug.Printf("Timeout")
|
|
|
|
return NetworkStatusConnectionTimeout, nil
|
|
}
|
|
|
|
conn, err := net.DialTimeout("tcp", dialAddress, 50*time.Millisecond)
|
|
if err != nil {
|
|
debug.Printf("Error: %s", err)
|
|
|
|
if strings.Contains(err.Error(), "i/o timeout") {
|
|
progressChan <- err
|
|
time.Sleep(time.Second)
|
|
|
|
continue
|
|
}
|
|
if strings.Contains(err.Error(), "connection refused") {
|
|
return NetworkStatusConnectionRefused, nil
|
|
}
|
|
return NetworkStatusErrorOther, errors.Wrap(err, "failed to dial")
|
|
}
|
|
|
|
if verifyConnectionToServer(conn, requestToken, responseToken) {
|
|
return NetworkStatusConnected, nil
|
|
}
|
|
|
|
progressChan <- errors.New("failed to verify connection to server")
|
|
time.Sleep(time.Second)
|
|
}
|
|
|
|
}
|
|
|
|
func handleTestConnection(conn net.Conn, requestToken []byte, responseToken []byte) bool {
|
|
defer func() {
|
|
if err := conn.Close(); err != nil {
|
|
debug.Println(err.Error())
|
|
}
|
|
}()
|
|
|
|
if err := conn.SetReadDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
|
|
debug.Printf("Server failed to set read deadline: %v\n", err)
|
|
return false
|
|
}
|
|
|
|
buf := make([]byte, 1024)
|
|
_, err := conn.Read(buf)
|
|
if err != nil {
|
|
debug.Printf("Server failed to read: %v\n", err)
|
|
return false
|
|
}
|
|
|
|
if !bytes.Contains(buf, requestToken) {
|
|
return false
|
|
}
|
|
|
|
if err := conn.SetWriteDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
|
|
debug.Printf("Server failed to set write deadline: %v\n", err)
|
|
return false
|
|
}
|
|
|
|
if _, err := conn.Write(responseToken); err != nil {
|
|
debug.Printf("Server failed to write: %v\n", err)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func verifyConnectionToServer(conn net.Conn, requestToken []byte, responseToken []byte) bool {
|
|
defer func() {
|
|
if err := conn.Close(); err != nil {
|
|
debug.Println(err.Error())
|
|
}
|
|
}()
|
|
|
|
if err := conn.SetWriteDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
|
|
debug.Printf("Client failed to set write deadline: %v\n", err)
|
|
return false
|
|
}
|
|
|
|
_, err := conn.Write(requestToken)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if err := conn.SetReadDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
|
|
debug.Printf("Client failed to set read deadline: %v\n", err)
|
|
return false
|
|
}
|
|
|
|
buf := make([]byte, 1024)
|
|
_, err = conn.Read(buf)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if !bytes.Contains(buf, responseToken) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|