Skip to content

Commit

Permalink
socat: add a utility to forward ports to a remote docker server (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicks authored Feb 3, 2021
1 parent 7f5e425 commit 8415124
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 0 deletions.
92 changes: 92 additions & 0 deletions internal/socat/socat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Manage socat network routers for remote docker instances.
package socat

import (
"context"
"fmt"
"net"
"os/exec"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
)

const serviceName = "ctlptl-portforward-service"

type ContainerClient interface {
ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error)
ContainerRemove(ctx context.Context, id string, options types.ContainerRemoveOptions) error
}

type Controller struct {
client ContainerClient
}

func NewController(client ContainerClient) *Controller {
return &Controller{client: client}
}

func DefaultController(ctx context.Context) (*Controller, error) {
client, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return nil, err
}

client.NegotiateAPIVersion(ctx)
return NewController(client), nil
}

// Connect a port on the local machine to a port on a remote docker machine.
func (c *Controller) ConnectRemoteDockerPort(ctx context.Context, port int) error {
err := c.StartRemotePortforwarder(ctx)
if err != nil {
return err
}
return c.StartLocalPortforwarder(ctx, port)
}

// Create a port-forwarding server on the same machine that's running
// Docker. This server accepts connections and routes them to localhost ports
// on the same machine.
func (c *Controller) StartRemotePortforwarder(ctx context.Context) error {
container, err := c.client.ContainerInspect(ctx, serviceName)
if err == nil && container.State.Running {
// The service is already running!
return nil
} else if err == nil {
// The service exists, but is not running
err := c.client.ContainerRemove(ctx, serviceName, types.ContainerRemoveOptions{Force: true})
if err != nil {
return fmt.Errorf("creating remote portforwarder: %v", err)
}
} else if !client.IsErrNotFound(err) {
return fmt.Errorf("inspecting remote portforwarder: %v", err)
}

cmd := exec.Command("docker", "run", "-d", "-it",
"--name", serviceName, "--net=host", "--restart=always",
"--entrypoint", "/bin/sh", "alpine/socat", "-c", "while true; do sleep 1000; done")
return cmd.Run()
}

// Create a port-forwarding server on the local machine, forwarding connections
// to the same port on the remote Docker server.
func (c *Controller) StartLocalPortforwarder(ctx context.Context, port int) error {
cmd := exec.Command("socat", fmt.Sprintf("TCP-LISTEN:%d,reuseaddr,fork", port),
fmt.Sprintf("EXEC:'docker exec -i %s socat STDIO TCP:localhost:%d'", serviceName, port))
err := cmd.Start()
if err != nil {
return fmt.Errorf("creating local portforwarder: %v", err)
}

for i := 0; i < 100; i++ {
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
if err == nil {
_ = conn.Close()
return nil
}
time.Sleep(100 * time.Millisecond)
}
return fmt.Errorf("timed out waiting for local portforwarder")
}
1 change: 1 addition & 0 deletions pkg/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func NewRootCommand() *cobra.Command {
rootCmd.AddCommand(NewDockerDesktopCommand())
rootCmd.AddCommand(newDocsCommand(rootCmd))
rootCmd.AddCommand(analytics.NewCommand())
rootCmd.AddCommand(NewSocatCommand())

return rootCmd
}
49 changes: 49 additions & 0 deletions pkg/cmd/socat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package cmd

import (
"context"
"fmt"
"os"
"strconv"

"github.com/spf13/cobra"
"github.com/tilt-dev/ctlptl/internal/socat"
)

func NewSocatCommand() *cobra.Command {
var cmd = &cobra.Command{
Use: "socat",
Short: "Use socat to connect components. Experimental.",
}

cmd.AddCommand(&cobra.Command{
Use: "connect-remote-docker",
Short: "Connects a local port to a remote port on a machine running Docker",
Example: " ctlptl socat connect-remote-docker [port]\n",
Run: connectRemoteDocker,
Args: cobra.ExactArgs(1),
})

return cmd
}

func connectRemoteDocker(cmd *cobra.Command, args []string) {
port, err := strconv.Atoi(args[0])
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "connect-remote-docker: %v\n", err)
os.Exit(1)
}

ctx := context.Background()
c, err := socat.DefaultController(ctx)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "connect-remote-docker: %v\n", err)
os.Exit(1)
}

err = c.ConnectRemoteDockerPort(ctx, port)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "connect-remote-docker: %v\n", err)
os.Exit(1)
}
}

0 comments on commit 8415124

Please sign in to comment.