sshtunnel/cmd/debug.go
2025-05-23 15:08:44 +02:00

207 lines
5.1 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cmd
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/spf13/cobra"
)
var debugCmd = &cobra.Command{
Use: "debug",
Short: "Run diagnostics on SSH tunnels",
Long: `Run diagnostic checks on your SSH tunnel setup, including:
- SSH client availability
- Tunnel directory integrity
- Recorded tunnels status
- Active SSH processes verification`,
Run: func(cmd *cobra.Command, args []string) {
runDebugCommand()
},
}
func init() {
rootCmd.AddCommand(debugCmd)
}
// runDebugCommand handles the debug subcommand logic
func runDebugCommand() {
fmt.Println("SSH Tunnel Manager Diagnostics")
fmt.Println("==============================")
// Check SSH client availability
fmt.Println("\n1. Checking SSH client:")
checkSSHClient()
// Check tunnel directory
fmt.Println("\n2. Checking tunnel directory:")
checkTunnelDirectory()
// Check recorded tunnels
fmt.Println("\n3. Checking recorded tunnels:")
checkRecordedTunnels()
// Check active SSH processes
fmt.Println("\n4. Checking active SSH processes:")
checkActiveSSHProcesses()
}
func checkSSHClient() {
path, err := exec.LookPath("ssh")
if err != nil {
fmt.Printf(" ❌ SSH client not found in PATH: %v\n", err)
return
}
fmt.Printf(" ✅ SSH client found at: %s\n", path)
// Get SSH version
cmd := exec.Command("ssh", "-V")
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf(" ⚠️ Could not determine SSH version: %v\n", err)
return
}
fmt.Printf(" ✅ SSH version info: %s\n", strings.TrimSpace(string(output)))
}
func checkTunnelDirectory() {
homeDir, err := os.UserHomeDir()
if err != nil {
fmt.Printf(" ❌ Could not determine home directory: %v\n", err)
return
}
tunnelPath := filepath.Join(homeDir, tunnelDir)
info, err := os.Stat(tunnelPath)
if os.IsNotExist(err) {
fmt.Printf(" ⚠️ Tunnel directory does not exist: %s\n", tunnelPath)
return
} else if err != nil {
fmt.Printf(" ❌ Error accessing tunnel directory: %v\n", err)
return
}
fmt.Printf(" ✅ Tunnel directory exists: %s\n", tunnelPath)
fmt.Printf(" ✅ Permissions: %s\n", info.Mode().String())
entries, err := os.ReadDir(tunnelPath)
if err != nil {
fmt.Printf(" ❌ Could not read tunnel directory: %v\n", err)
return
}
fmt.Printf(" ✅ Directory contains %d entries\n", len(entries))
}
func checkRecordedTunnels() {
tunnels, err := getTunnels()
if err != nil {
fmt.Printf(" ❌ Error reading tunnels: %v\n", err)
return
}
if len(tunnels) == 0 {
fmt.Printf(" No recorded tunnels found\n")
return
}
fmt.Printf(" ✅ Found %d recorded tunnels\n", len(tunnels))
for i, t := range tunnels {
fmt.Printf("\n Tunnel #%d (ID: %d):\n", i+1, t.ID)
fmt.Printf(" Local port: %d\n", t.LocalPort)
fmt.Printf(" Remote: %s:%d\n", t.RemoteHost, t.RemotePort)
fmt.Printf(" Server: %s\n", t.SSHServer)
fmt.Printf(" PID: %d\n", t.PID)
// Check if process exists
process, err := os.FindProcess(t.PID)
if err != nil {
fmt.Printf(" ❌ Process not found: %v\n", err)
continue
}
// Try to send signal 0 to check if process exists
err = process.Signal(syscall.Signal(0))
if err != nil {
fmt.Printf(" ❌ Process not running: %v\n", err)
} else {
fmt.Printf(" ✅ Process is running\n")
// Check if it's actually an SSH process
isSSH := checkIfSSHProcess(t.PID)
if isSSH {
fmt.Printf(" ✅ Process is an SSH process\n")
} else {
fmt.Printf(" ⚠️ Process is not an SSH process!\n")
}
}
}
}
func checkActiveSSHProcesses() {
// Try using ps to find SSH processes
cmd := exec.Command("ps", "-eo", "pid,command")
output, err := cmd.Output()
if err != nil {
fmt.Printf(" ❌ Could not list processes: %v\n", err)
return
}
lines := strings.Split(string(output), "\n")
sshProcesses := []string{}
for _, line := range lines {
if strings.Contains(line, "ssh") && strings.Contains(line, "-L") {
sshProcesses = append(sshProcesses, strings.TrimSpace(line))
}
}
if len(sshProcesses) == 0 {
fmt.Printf(" No SSH tunnel processes found\n")
return
}
fmt.Printf(" ✅ Found %d SSH tunnel processes:\n", len(sshProcesses))
for _, proc := range sshProcesses {
fmt.Printf(" %s\n", proc)
// Extract PID
fields := strings.Fields(proc)
if len(fields) > 0 {
pid, err := strconv.Atoi(fields[0])
if err == nil {
// Check if this process is in our records
found := false
tunnels, _ := getTunnels()
for _, t := range tunnels {
if t.PID == pid {
fmt.Printf(" ✅ This process is tracked as tunnel ID %d\n", t.ID)
found = true
break
}
}
if !found {
fmt.Printf(" ⚠️ This process is not tracked by the tunnel manager\n")
}
}
}
}
}
func verifyTunnelConnectivity(t Tunnel) error {
// Try to connect to the local port to verify the tunnel is working
cmd := exec.Command("nc", "-z", "-w", "1", "localhost", strconv.Itoa(t.LocalPort))
err := cmd.Run()
if err != nil {
return fmt.Errorf("could not connect to local port %d: %v", t.LocalPort, err)
}
return nil
}