Skip to content

Commit b34267c

Browse files
committed
limactl: add tunnel command
```console $ limactl tunnel default Open <System Settings> → <Network> → <Wi-Fi> (or whatever) → <Details> → <Proxies> → <SOCKS proxy>, and specify the following configuration: - Server: 127.0.0.1 - Port: 54940 The instance can be connected from the host as <http://lima-default.internal> via a web browser. $ curl --proxy socks5h://127.0.0.1:54940 http://lima-default.internal <!DOCTYPE html> [...] ``` Signed-off-by: Akihiro Suda <[email protected]>
1 parent 7a1d74a commit b34267c

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed

Diff for: cmd/limactl/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ func newApp() *cobra.Command {
154154
newSnapshotCommand(),
155155
newProtectCommand(),
156156
newUnprotectCommand(),
157+
newTunnelCommand(),
157158
)
158159
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
159160
rootCmd.AddCommand(startAtLoginCommand())

Diff for: cmd/limactl/tunnel.go

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"runtime"
9+
"strconv"
10+
11+
"github.com/lima-vm/lima/pkg/freeport"
12+
"github.com/lima-vm/lima/pkg/sshutil"
13+
"github.com/lima-vm/lima/pkg/store"
14+
"github.com/mattn/go-shellwords"
15+
"github.com/sirupsen/logrus"
16+
"github.com/spf13/cobra"
17+
)
18+
19+
const tunnelHelp = `Create a tunnel for Lima
20+
21+
Create a SOCKS tunnel so that the host can join the guest network.
22+
`
23+
24+
func newTunnelCommand() *cobra.Command {
25+
tunnelCmd := &cobra.Command{
26+
Use: "tunnel [flags] INSTANCE",
27+
Short: "Create a tunnel for Lima",
28+
PersistentPreRun: func(*cobra.Command, []string) {
29+
logrus.Warn("`limactl tunnel` is experimental")
30+
},
31+
Long: tunnelHelp,
32+
Args: WrapArgsError(cobra.ExactArgs(1)),
33+
RunE: tunnelAction,
34+
ValidArgsFunction: tunnelBashComplete,
35+
SilenceErrors: true,
36+
GroupID: advancedCommand,
37+
}
38+
39+
tunnelCmd.Flags().SetInterspersed(false)
40+
// TODO: implement l2tp, ikev2, masque, ...
41+
tunnelCmd.Flags().String("type", "socks", "Tunnel type, currently only \"socks\" is implemented")
42+
tunnelCmd.Flags().Int("socks-port", 0, "SOCKS port, defaults to a random port")
43+
return tunnelCmd
44+
}
45+
46+
func tunnelAction(cmd *cobra.Command, args []string) error {
47+
flags := cmd.Flags()
48+
tunnelType, err := flags.GetString("type")
49+
if err != nil {
50+
return err
51+
}
52+
if tunnelType != "socks" {
53+
return fmt.Errorf("unknown tunnel type: %q", tunnelType)
54+
}
55+
port, err := flags.GetInt("socks-port")
56+
if err != nil {
57+
return err
58+
}
59+
if port != 0 && (port < 1024 || port > 65535) {
60+
return fmt.Errorf("invalid socks port %d", port)
61+
}
62+
stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()
63+
instName := args[0]
64+
inst, err := store.Inspect(instName)
65+
if err != nil {
66+
if errors.Is(err, os.ErrNotExist) {
67+
return fmt.Errorf("instance %q does not exist, run `limactl create %s` to create a new instance", instName, instName)
68+
}
69+
return err
70+
}
71+
if inst.Status == store.StatusStopped {
72+
return fmt.Errorf("instance %q is stopped, run `limactl start %s` to start the instance", instName, instName)
73+
}
74+
75+
if port == 0 {
76+
port, err = freeport.TCP()
77+
if err != nil {
78+
return err
79+
}
80+
}
81+
82+
var (
83+
arg0 string
84+
arg0Args []string
85+
)
86+
// FIXME: deduplicate the code clone across `limactl shell` and `limactl tunnel`
87+
if sshShell := os.Getenv(envShellSSH); sshShell != "" {
88+
sshShellFields, err := shellwords.Parse(sshShell)
89+
switch {
90+
case err != nil:
91+
logrus.WithError(err).Warnf("Failed to split %s variable into shell tokens. "+
92+
"Falling back to 'ssh' command", envShellSSH)
93+
case len(sshShellFields) > 0:
94+
arg0 = sshShellFields[0]
95+
if len(sshShellFields) > 1 {
96+
arg0Args = sshShellFields[1:]
97+
}
98+
}
99+
}
100+
101+
if arg0 == "" {
102+
arg0, err = exec.LookPath("ssh")
103+
if err != nil {
104+
return err
105+
}
106+
}
107+
108+
sshOpts, err := sshutil.SSHOpts(
109+
inst.Dir,
110+
*inst.Config.SSH.LoadDotSSHPubKeys,
111+
*inst.Config.SSH.ForwardAgent,
112+
*inst.Config.SSH.ForwardX11,
113+
*inst.Config.SSH.ForwardX11Trusted)
114+
if err != nil {
115+
return err
116+
}
117+
sshArgs := sshutil.SSHArgsFromOpts(sshOpts)
118+
sshArgs = append(sshArgs, []string{
119+
"-q", // quiet
120+
"-f", // background
121+
"-N", // no command
122+
"-D", fmt.Sprintf("127.0.0.1:%d", port),
123+
"-p", strconv.Itoa(inst.SSHLocalPort),
124+
inst.SSHAddress,
125+
}...)
126+
sshCmd := exec.Command(arg0, append(arg0Args, sshArgs...)...)
127+
sshCmd.Stdout = stderr
128+
sshCmd.Stderr = stderr
129+
logrus.Debugf("executing ssh (may take a long)): %+v", sshCmd.Args)
130+
131+
if err := sshCmd.Run(); err != nil {
132+
return err
133+
}
134+
135+
switch runtime.GOOS {
136+
case "darwin":
137+
fmt.Fprintf(stdout, "Open <System Settings> → <Network> → <Wi-Fi> (or whatever) → <Details> → <Proxies> → <SOCKS proxy>,\n")
138+
fmt.Fprintf(stdout, "and specify the following configuration:\n")
139+
fmt.Fprintf(stdout, "- Server: 127.0.0.1\n")
140+
fmt.Fprintf(stdout, "- Port: %d\n", port)
141+
case "windows":
142+
fmt.Fprintf(stdout, "Open <Settings> → <Network & Internet> → <Proxy>,\n")
143+
fmt.Fprintf(stdout, "and specify the following configuration:\n")
144+
fmt.Fprintf(stdout, "- Address: socks=127.0.0.1\n")
145+
fmt.Fprintf(stdout, "- Port: %d\n", port)
146+
default:
147+
fmt.Fprintf(stdout, "Set `ALL_PROXY=socks5h://127.0.0.1:%d`, etc.\n", port)
148+
}
149+
fmt.Fprintf(stdout, "The instance can be connected from the host as <http://lima-%s.internal> via a web browser.\n", inst.Name)
150+
151+
// TODO: show the port in `limactl list --json` ?
152+
// TODO: add `--stop` flag to shut down the tunnel
153+
return nil
154+
}
155+
156+
func tunnelBashComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
157+
return bashCompleteInstanceNames(cmd)
158+
}

Diff for: website/content/en/docs/releases/experimental/_index.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ The following features are experimental and subject to change:
1717
The following commands are experimental and subject to change:
1818

1919
- `limactl snapshot *`
20+
- `limactl tunnel`
2021

2122
## Graduated
2223

0 commit comments

Comments
 (0)