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