Improve port mapping to prefer same local port
Enhanced port forwarding logic to be more intuitive: - Try to map remote port to same local port when possible - Fallback to random available port if same port unavailable - Clear user feedback showing port mapping (same vs different) - Enhanced forwarding view with access URLs and instructions - Added --test-port command to test port mapping logic Examples: - Remote port 3000 -> localhost:3000 (if available) - Remote port 80 -> localhost:random (if 80 unavailable) - Shows 'same port' or 'port X was unavailable' messages This makes port forwarding much more intuitive - users can access localhost:3000 when forwarding remote port 3000. Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
43
main.go
43
main.go
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,6 +22,12 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for port mapping test mode
|
||||||
|
if len(os.Args) > 2 && os.Args[1] == "--test-port" {
|
||||||
|
testPortMapping(os.Args[2])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the application
|
// Initialize the application
|
||||||
app := NewApp()
|
app := NewApp()
|
||||||
if err := app.Run(); err != nil {
|
if err := app.Run(); err != nil {
|
||||||
@@ -62,6 +69,7 @@ func testMode() {
|
|||||||
fmt.Println("Note: TUI requires a proper terminal environment")
|
fmt.Println("Note: TUI requires a proper terminal environment")
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
fmt.Println("To test connection to a specific host: ./kport --test-connect <hostname>")
|
fmt.Println("To test connection to a specific host: ./kport --test-connect <hostname>")
|
||||||
|
fmt.Println("To test port mapping logic: ./kport --test-port <port>")
|
||||||
}
|
}
|
||||||
|
|
||||||
// testConnection tests connecting to a specific host
|
// testConnection tests connecting to a specific host
|
||||||
@@ -183,3 +191,38 @@ func expandShellVars(value string) string {
|
|||||||
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testPortMapping tests the port mapping logic
|
||||||
|
func testPortMapping(portStr string) {
|
||||||
|
fmt.Printf("Testing port mapping for port: %s\n", portStr)
|
||||||
|
fmt.Println("=====================================")
|
||||||
|
|
||||||
|
remotePort, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("❌ Invalid port number: %s\n", portStr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if remotePort <= 0 || remotePort > 65535 {
|
||||||
|
fmt.Printf("❌ Port number must be between 1 and 65535\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the port mapping logic
|
||||||
|
localPort, samePort, err := findPreferredLocalPort(remotePort)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("❌ Failed to find available port: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if samePort {
|
||||||
|
fmt.Printf("✅ Port %d is available locally - using same port\n", localPort)
|
||||||
|
fmt.Printf(" Mapping: localhost:%d -> remote:%d\n", localPort, remotePort)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("⚠️ Port %d is unavailable locally - using alternative port %d\n", remotePort, localPort)
|
||||||
|
fmt.Printf(" Mapping: localhost:%d -> remote:%d\n", localPort, remotePort)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println("This is how kport will map the ports when forwarding.")
|
||||||
|
}
|
@@ -119,13 +119,17 @@ func StartPortForwarding(host SSHHost, remotePort int) tea.Cmd {
|
|||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
fmt.Fprintf(os.Stderr, "Debug: Starting port forwarding for %s:%d\n", host.Name, remotePort)
|
fmt.Fprintf(os.Stderr, "Debug: Starting port forwarding for %s:%d\n", host.Name, remotePort)
|
||||||
|
|
||||||
// Find an available local port
|
// Try to use the same port locally, fallback to random if unavailable
|
||||||
localPort, err := findAvailablePort()
|
localPort, samePort, err := findPreferredLocalPort(remotePort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Debug: Failed to find available port: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Debug: Failed to find available port: %v\n", err)
|
||||||
return ErrorMsg{Error: fmt.Errorf("failed to find available local port: %w", err)}
|
return ErrorMsg{Error: fmt.Errorf("failed to find available local port: %w", err)}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "Debug: Found available local port: %d\n", localPort)
|
if samePort {
|
||||||
|
fmt.Fprintf(os.Stderr, "Debug: Using same port locally: %d\n", localPort)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "Debug: Port %d unavailable, using alternative: %d\n", remotePort, localPort)
|
||||||
|
}
|
||||||
|
|
||||||
// Create and start port forwarder using ssh command
|
// Create and start port forwarder using ssh command
|
||||||
forwarder := NewPortForwarder(host.Name, localPort, remotePort)
|
forwarder := NewPortForwarder(host.Name, localPort, remotePort)
|
||||||
@@ -158,13 +162,17 @@ func StartManualPortForwarding(host SSHHost, portStr string) tea.Cmd {
|
|||||||
return ErrorMsg{Error: fmt.Errorf("port number must be between 1 and 65535")}
|
return ErrorMsg{Error: fmt.Errorf("port number must be between 1 and 65535")}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find an available local port
|
// Try to use the same port locally, fallback to random if unavailable
|
||||||
localPort, err := findAvailablePort()
|
localPort, samePort, err := findPreferredLocalPort(remotePort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Debug: Failed to find available port: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Debug: Failed to find available port: %v\n", err)
|
||||||
return ErrorMsg{Error: fmt.Errorf("failed to find available local port: %w", err)}
|
return ErrorMsg{Error: fmt.Errorf("failed to find available local port: %w", err)}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "Debug: Found available local port: %d\n", localPort)
|
if samePort {
|
||||||
|
fmt.Fprintf(os.Stderr, "Debug: Using same port locally: %d\n", localPort)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "Debug: Port %d unavailable, using alternative: %d\n", remotePort, localPort)
|
||||||
|
}
|
||||||
|
|
||||||
// Create and start port forwarder using ssh command
|
// Create and start port forwarder using ssh command
|
||||||
forwarder := NewPortForwarder(host.Name, localPort, remotePort)
|
forwarder := NewPortForwarder(host.Name, localPort, remotePort)
|
||||||
@@ -183,6 +191,32 @@ func StartManualPortForwarding(host SSHHost, portStr string) tea.Cmd {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// findPreferredLocalPort tries to use the same port as remote, fallback to random
|
||||||
|
func findPreferredLocalPort(remotePort int) (localPort int, samePort bool, err error) {
|
||||||
|
// First try to use the same port as the remote port
|
||||||
|
if isPortAvailable(remotePort) {
|
||||||
|
return remotePort, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If same port is not available, find any available port
|
||||||
|
availablePort, err := findAvailablePort()
|
||||||
|
if err != nil {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return availablePort, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isPortAvailable checks if a specific port is available locally
|
||||||
|
func isPortAvailable(port int) bool {
|
||||||
|
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
listener.Close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// findAvailablePort finds an available local port
|
// findAvailablePort finds an available local port
|
||||||
func findAvailablePort() (int, error) {
|
func findAvailablePort() (int, error) {
|
||||||
listener, err := net.Listen("tcp", ":0")
|
listener, err := net.Listen("tcp", ":0")
|
||||||
|
30
tui.go
30
tui.go
@@ -93,8 +93,13 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
case ForwardingStartedMsg:
|
case ForwardingStartedMsg:
|
||||||
m.message = fmt.Sprintf("Port forwarding started: localhost:%d -> %s:%d",
|
if msg.LocalPort == msg.RemotePort {
|
||||||
msg.LocalPort, m.hosts[m.selectedHost].Name, msg.RemotePort)
|
m.message = fmt.Sprintf("Port forwarding started: localhost:%d -> %s:%d (same port)",
|
||||||
|
msg.LocalPort, m.hosts[m.selectedHost].Name, msg.RemotePort)
|
||||||
|
} else {
|
||||||
|
m.message = fmt.Sprintf("Port forwarding started: localhost:%d -> %s:%d (port %d was unavailable)",
|
||||||
|
msg.LocalPort, m.hosts[m.selectedHost].Name, msg.RemotePort, msg.RemotePort)
|
||||||
|
}
|
||||||
m.state = StateForwarding
|
m.state = StateForwarding
|
||||||
return m, nil
|
return m, nil
|
||||||
case ErrorMsg:
|
case ErrorMsg:
|
||||||
@@ -474,6 +479,27 @@ func (m *Model) renderForwarding() string {
|
|||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
s.WriteString(m.message)
|
s.WriteString(m.message)
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
|
|
||||||
|
// Add helpful access information
|
||||||
|
accessStyle := lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color("#7D56F4")).
|
||||||
|
Bold(true)
|
||||||
|
|
||||||
|
s.WriteString(accessStyle.Render("Access your service:"))
|
||||||
|
s.WriteString("\n")
|
||||||
|
|
||||||
|
// Extract local port from message for display
|
||||||
|
if strings.Contains(m.message, "localhost:") {
|
||||||
|
parts := strings.Split(m.message, "localhost:")
|
||||||
|
if len(parts) > 1 {
|
||||||
|
portPart := strings.Split(parts[1], " ")[0]
|
||||||
|
s.WriteString(fmt.Sprintf(" • http://localhost:%s\n", portPart))
|
||||||
|
s.WriteString(fmt.Sprintf(" • https://localhost:%s\n", portPart))
|
||||||
|
s.WriteString(fmt.Sprintf(" • Or connect to localhost:%s with any client\n", portPart))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.WriteString("\n")
|
||||||
s.WriteString("Controls:\n")
|
s.WriteString("Controls:\n")
|
||||||
s.WriteString(" Esc: Stop forwarding and return q: Quit\n")
|
s.WriteString(" Esc: Stop forwarding and return q: Quit\n")
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user