18
winio "github.com/Microsoft/go-winio"
19
"github.com/containers/podman/v5/pkg/machine/define"
20
"github.com/containers/podman/v5/pkg/machine/env"
21
"github.com/containers/storage/pkg/fileutils"
22
"github.com/sirupsen/logrus"
26
NamedPipePrefix = "npipe:////./pipe/"
27
GlobalNamedPipe = "docker_engine"
28
winSSHProxy = "win-sshproxy.exe"
29
winSSHProxyTid = "win-sshproxy.tid"
30
rootfulSock = "/run/podman/podman.sock"
31
rootlessSock = "/run/user/1000/podman/podman.sock"
34
MachineNameWait = 5 * time.Second
35
GlobalNameWait = 250 * time.Millisecond
41
type WinProxyOpts struct {
50
func GetProcessState(pid int) (active bool, exitCode int) {
51
const da = syscall.STANDARD_RIGHTS_READ | syscall.PROCESS_QUERY_INFORMATION | syscall.SYNCHRONIZE
52
handle, err := syscall.OpenProcess(da, false, uint32(pid))
54
logrus.Debugf("Error retrieving process %d: %v", pid, err)
55
return false, int(syscall.ERROR_PROC_NOT_FOUND)
59
if err := syscall.GetExitCodeProcess(handle, &code); err != nil {
60
logrus.Errorf("Error retrieving process %d exit code: %v", pid, err)
62
return code == 259, int(code)
65
func PipeNameAvailable(pipeName string, maxWait time.Duration) bool {
66
const interval = 250 * time.Millisecond
67
var wait time.Duration
69
err := fileutils.Exists(`\\.\pipe\` + pipeName)
70
if errors.Is(err, fs.ErrNotExist) {
81
func WaitPipeExists(pipeName string, retries int, checkFailure func() error) error {
83
for i := 0; i < retries; i++ {
84
err = fileutils.Exists(`\\.\pipe\` + pipeName)
88
if fail := checkFailure(); fail != nil {
91
time.Sleep(250 * time.Millisecond)
97
func DialNamedPipe(ctx context.Context, path string) (net.Conn, error) {
98
path = strings.ReplaceAll(path, "/", "\\")
99
return winio.DialPipeContext(ctx, path)
102
func LaunchWinProxy(opts WinProxyOpts, noInfo bool) {
103
globalName, pipeName, err := launchWinProxy(opts)
106
fmt.Fprintln(os.Stderr, "API forwarding for Docker API clients is not available due to the following startup failures.")
107
fmt.Fprintf(os.Stderr, "\t%s\n", err.Error())
108
fmt.Fprintln(os.Stderr, "\nPodman clients are still able to connect.")
110
fmt.Printf("API forwarding listening on: %s\n", pipeName)
112
fmt.Printf("\nDocker API clients default to this address. You do not need to set DOCKER_HOST.\n")
114
fmt.Printf("\nAnother process was listening on the default Docker API pipe address.\n")
115
fmt.Printf("You can still connect Docker API clients by setting DOCKER HOST using the\n")
116
fmt.Printf("following powershell command in your terminal session:\n")
117
fmt.Printf("\n\t$Env:DOCKER_HOST = '%s'\n", pipeName)
118
fmt.Printf("\nOr in a classic CMD prompt:\n")
119
fmt.Printf("\n\tset DOCKER_HOST=%s\n", pipeName)
120
fmt.Printf("\nAlternatively, terminate the other process and restart podman machine.\n")
126
func launchWinProxy(opts WinProxyOpts) (bool, string, error) {
127
machinePipe := env.WithPodmanPrefix(opts.Name)
128
if !PipeNameAvailable(machinePipe, MachineNameWait) {
129
return false, "", fmt.Errorf("could not start api proxy since expected pipe is not available: %s", machinePipe)
133
if PipeNameAvailable(GlobalNamedPipe, GlobalNameWait) {
137
command, err := FindExecutablePeer(winSSHProxy)
139
return globalName, "", err
142
stateDir, err := GetWinProxyStateDir(opts.Name, opts.VMType)
144
return globalName, "", err
147
destSock := rootlessSock
148
forwardUser := opts.RemoteUsername
151
destSock = rootfulSock
155
dest := fmt.Sprintf("ssh://%s@localhost:%d%s", forwardUser, opts.Port, destSock)
156
args := []string{opts.Name, stateDir, NamedPipePrefix + machinePipe, dest, opts.IdentityPath}
157
waitPipe := machinePipe
159
args = append(args, NamedPipePrefix+GlobalNamedPipe, dest, opts.IdentityPath)
160
waitPipe = GlobalNamedPipe
163
cmd := exec.Command(command, args...)
164
logrus.Debugf("winssh command: %s %v", command, args)
165
if err := cmd.Start(); err != nil {
166
return globalName, "", err
169
return globalName, NamedPipePrefix + waitPipe, WaitPipeExists(waitPipe, 80, func() error {
170
active, exitCode := GetProcessState(cmd.Process.Pid)
172
return fmt.Errorf("win-sshproxy.exe failed to start, exit code: %d (see windows event logs)", exitCode)
179
func StopWinProxy(name string, vmtype define.VMType) error {
180
pid, tid, tidFile, err := readWinProxyTid(name, vmtype)
185
proc, err := os.FindProcess(int(pid))
191
_ = waitTimeout(proc, 20*time.Second)
192
_ = os.Remove(tidFile)
197
func readWinProxyTid(name string, vmtype define.VMType) (uint32, uint32, string, error) {
198
stateDir, err := GetWinProxyStateDir(name, vmtype)
203
tidFile := filepath.Join(stateDir, winSSHProxyTid)
204
contents, err := os.ReadFile(tidFile)
210
fmt.Sscanf(string(contents), "%d:%d", &pid, &tid)
211
return pid, tid, tidFile, nil
214
func waitTimeout(proc *os.Process, timeout time.Duration) bool {
215
done := make(chan bool)
222
case <-time.After(timeout):
233
func sendQuit(tid uint32) {
234
user32 := syscall.NewLazyDLL("user32.dll")
235
postMessage := user32.NewProc("PostThreadMessageW")
237
_, _, _ = postMessage.Call(uintptr(tid), WM_QUIT, 0, 0)
240
func FindExecutablePeer(name string) (string, error) {
241
exe, err := os.Executable()
246
exe, err = filepath.EvalSymlinks(exe)
251
return filepath.Join(filepath.Dir(exe), name), nil
254
func GetWinProxyStateDir(name string, vmtype define.VMType) (string, error) {
255
dir, err := env.GetDataDir(vmtype)
259
stateDir := filepath.Join(dir, name)
260
if err = os.MkdirAll(stateDir, 0755); err != nil {
267
func GetEnvSetString(env string, val string) string {
268
return fmt.Sprintf("$Env:%s=\"%s\"", env, val)