mirror of
https://github.com/Gu1llaum-3/sshm.git
synced 2026-03-13 19:31:44 +01:00
Merge pull request #44 from fgbm/main
Fix: connectivity check for hosts using ProxyJump or ProxyCommand
This commit is contained in:
@@ -2,12 +2,15 @@ package connectivity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"github.com/Gu1llaum-3/sshm/internal/config"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Gu1llaum-3/sshm/internal/config"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
@@ -45,16 +48,18 @@ type HostPingResult struct {
|
||||
|
||||
// PingManager manages SSH connectivity checks for multiple hosts
|
||||
type PingManager struct {
|
||||
results map[string]*HostPingResult
|
||||
mutex sync.RWMutex
|
||||
timeout time.Duration
|
||||
results map[string]*HostPingResult
|
||||
mutex sync.RWMutex
|
||||
timeout time.Duration
|
||||
configFile string
|
||||
}
|
||||
|
||||
// NewPingManager creates a new ping manager with the specified timeout
|
||||
func NewPingManager(timeout time.Duration) *PingManager {
|
||||
func NewPingManager(timeout time.Duration, configFile string) *PingManager {
|
||||
return &PingManager{
|
||||
results: make(map[string]*HostPingResult),
|
||||
timeout: timeout,
|
||||
results: make(map[string]*HostPingResult),
|
||||
timeout: timeout,
|
||||
configFile: configFile,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +103,14 @@ func (pm *PingManager) PingHost(ctx context.Context, host config.SSHHost) *HostP
|
||||
// Mark as connecting
|
||||
pm.updateStatus(host.Name, StatusConnecting, nil, 0)
|
||||
|
||||
// If the host uses a ProxyJump or ProxyCommand, we need to use the external SSH command
|
||||
// because implementing jump host support with pure Go ssh library requires
|
||||
// handling authentication for the jump host, which is complex and requires
|
||||
// access to the user's SSH agent or keys.
|
||||
if host.ProxyJump != "" || host.ProxyCommand != "" {
|
||||
return pm.pingWithExternalCommand(ctx, host, start)
|
||||
}
|
||||
|
||||
// Determine the actual hostname and port
|
||||
hostname := host.Hostname
|
||||
if hostname == "" {
|
||||
@@ -159,6 +172,53 @@ func (pm *PingManager) PingHost(ctx context.Context, host config.SSHHost) *HostP
|
||||
}
|
||||
}
|
||||
|
||||
// pingWithExternalCommand pings a host using the external SSH command
|
||||
func (pm *PingManager) pingWithExternalCommand(ctx context.Context, host config.SSHHost, start time.Time) *HostPingResult {
|
||||
// Construct the SSH command
|
||||
// ssh -q -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=5 host exit
|
||||
args := []string{"-q", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=no"}
|
||||
|
||||
// Set timeout matching the manager's timeout
|
||||
// Convert duration to seconds (rounding up to ensure we don't timeout too early in the command)
|
||||
timeoutSec := int(pm.timeout.Seconds())
|
||||
if timeoutSec < 1 {
|
||||
timeoutSec = 1
|
||||
}
|
||||
args = append(args, "-o", fmt.Sprintf("ConnectTimeout=%d", timeoutSec))
|
||||
|
||||
// If we have a specific config file, use it
|
||||
if pm.configFile != "" {
|
||||
args = append(args, "-F", pm.configFile)
|
||||
}
|
||||
|
||||
// Add the host name and the command to run (exit)
|
||||
args = append(args, host.Name, "exit")
|
||||
|
||||
// Create command with context for timeout cancellation
|
||||
// Note: We used pm.timeout for the ssh command option, but we also respect the context deadline
|
||||
cmd := exec.CommandContext(ctx, "ssh", args...)
|
||||
|
||||
// Run the command
|
||||
err := cmd.Run()
|
||||
duration := time.Since(start)
|
||||
|
||||
var status PingStatus
|
||||
if err != nil {
|
||||
// SSH returns non-zero exit code on connection failure
|
||||
status = StatusOffline
|
||||
} else {
|
||||
status = StatusOnline
|
||||
}
|
||||
|
||||
pm.updateStatus(host.Name, status, err, duration)
|
||||
return &HostPingResult{
|
||||
HostName: host.Name,
|
||||
Status: status,
|
||||
Error: err,
|
||||
Duration: duration,
|
||||
}
|
||||
}
|
||||
|
||||
// PingAllHosts pings all hosts concurrently and returns a channel of results
|
||||
func (pm *PingManager) PingAllHosts(ctx context.Context, hosts []config.SSHHost) <-chan *HostPingResult {
|
||||
resultChan := make(chan *HostPingResult, len(hosts))
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func TestNewPingManager(t *testing.T) {
|
||||
pm := NewPingManager(5 * time.Second)
|
||||
pm := NewPingManager(5*time.Second, "")
|
||||
if pm == nil {
|
||||
t.Error("NewPingManager() returned nil")
|
||||
}
|
||||
@@ -19,7 +19,7 @@ func TestNewPingManager(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPingManager_PingHost(t *testing.T) {
|
||||
pm := NewPingManager(1 * time.Second)
|
||||
pm := NewPingManager(1*time.Second, "")
|
||||
ctx := context.Background()
|
||||
|
||||
// Test ping method exists and doesn't panic
|
||||
@@ -38,7 +38,7 @@ func TestPingManager_PingHost(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPingManager_GetStatus(t *testing.T) {
|
||||
pm := NewPingManager(1 * time.Second)
|
||||
pm := NewPingManager(1*time.Second, "")
|
||||
|
||||
// Test unknown host
|
||||
status := pm.GetStatus("unknown.host")
|
||||
@@ -57,7 +57,7 @@ func TestPingManager_GetStatus(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPingManager_PingMultipleHosts(t *testing.T) {
|
||||
pm := NewPingManager(1 * time.Second)
|
||||
pm := NewPingManager(1*time.Second, "")
|
||||
hosts := []config.SSHHost{
|
||||
{Name: "localhost", Hostname: "127.0.0.1", Port: "22"},
|
||||
{Name: "invalid", Hostname: "invalid.host.12345", Port: "22"},
|
||||
@@ -81,7 +81,7 @@ func TestPingManager_PingMultipleHosts(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPingManager_GetResult(t *testing.T) {
|
||||
pm := NewPingManager(1 * time.Second)
|
||||
pm := NewPingManager(1*time.Second, "")
|
||||
ctx := context.Background()
|
||||
|
||||
// Test getting result for unknown host
|
||||
@@ -126,7 +126,7 @@ func TestPingStatus_String(t *testing.T) {
|
||||
|
||||
func TestPingHost_Basic(t *testing.T) {
|
||||
// Test that the ping functionality exists
|
||||
pm := NewPingManager(1 * time.Second)
|
||||
pm := NewPingManager(1*time.Second, "")
|
||||
ctx := context.Background()
|
||||
host := config.SSHHost{Name: "test", Hostname: "127.0.0.1", Port: "22"}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ func NewModel(hosts []config.SSHHost, configFile string, searchMode bool, curren
|
||||
styles := NewStyles(80) // Default width
|
||||
|
||||
// Initialize ping manager with 5 second timeout
|
||||
pingManager := connectivity.NewPingManager(5 * time.Second)
|
||||
pingManager := connectivity.NewPingManager(5*time.Second, configFile)
|
||||
|
||||
// Create the model with default sorting by name
|
||||
m := Model{
|
||||
|
||||
Reference in New Issue
Block a user