package cmd import ( "fmt" "net" "sync" "github.com/gorilla/websocket" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) type TunnelMessage struct { Type string `json:"type"` StreamID string `json:"stream_id"` Data []byte `json:"data,omitempty"` } var serverAddr string var linkCmd = &cobra.Command{ Use: "link ", Short: "Create a tunnel link", Args: cobra.ExactArgs(2), Run: func(cmd *cobra.Command, args []string) { vhostLoc := args[0] hostPort := args[1] fmt.Printf("Creating TCP tunnel: %s -> %s\n", vhostLoc, hostPort) if err := startTCPTunnel(vhostLoc, hostPort); err != nil { log.Fatal("Failed to start tunnel:", err) } }, } func init() { linkCmd.Flags().StringVarP(&serverAddr, "server", "s", "localhost:8080", "Conduit server address") } func startTCPTunnel(vhost, hostPort string) error { wsURL := fmt.Sprintf("ws://%s/_conduit/tunnel?vhost=%s", serverAddr, vhost) conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil) if err != nil { return fmt.Errorf("failed to connect: %v", err) } defer conn.Close() fmt.Printf("TCP Tunnel active! %s.example.com -> %s\n", vhost, hostPort) // Track active connections connections := make(map[string]net.Conn) var connMutex sync.RWMutex // Handle messages from server for { var msg TunnelMessage err := conn.ReadJSON(&msg) if err != nil { log.Errorf("Error reading from tunnel: %v", err) break } switch msg.Type { case "data": connMutex.RLock() localConn, exists := connections[msg.StreamID] connMutex.RUnlock() if !exists { // New connection localConn, err = net.Dial("tcp", hostPort) if err != nil { log.Errorf("Failed to connect to %s: %v", hostPort, err) continue } connMutex.Lock() connections[msg.StreamID] = localConn connMutex.Unlock() // Start reading from local connection go func(streamID string, lConn net.Conn) { defer func() { connMutex.Lock() delete(connections, streamID) connMutex.Unlock() lConn.Close() }() buffer := make([]byte, 4096) for { n, err := lConn.Read(buffer) if err != nil { break } response := TunnelMessage{ Type: "data", StreamID: streamID, Data: buffer[:n], } if err := conn.WriteJSON(response); err != nil { break } } }(msg.StreamID, localConn) } // Write data to local connection if _, err := localConn.Write(msg.Data); err != nil { log.Errorf("Error writing to local connection: %v", err) localConn.Close() connMutex.Lock() delete(connections, msg.StreamID) connMutex.Unlock() } case "close": connMutex.Lock() if localConn, exists := connections[msg.StreamID]; exists { localConn.Close() delete(connections, msg.StreamID) } connMutex.Unlock() } } return nil }