github.com/rish1988/moby@v25.0.2+incompatible/libnetwork/portmapper/proxy_linux.go (about) 1 package portmapper 2 3 import ( 4 "fmt" 5 "io" 6 "net" 7 "os" 8 "os/exec" 9 "runtime" 10 "strconv" 11 "syscall" 12 "time" 13 ) 14 15 func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int, proxyPath string) (userlandProxy, error) { 16 if proxyPath == "" { 17 return nil, fmt.Errorf("no path provided for userland-proxy binary") 18 } 19 20 return &proxyCommand{ 21 cmd: &exec.Cmd{ 22 Path: proxyPath, 23 Args: []string{ 24 proxyPath, 25 "-proto", proto, 26 "-host-ip", hostIP.String(), 27 "-host-port", strconv.Itoa(hostPort), 28 "-container-ip", containerIP.String(), 29 "-container-port", strconv.Itoa(containerPort), 30 }, 31 SysProcAttr: &syscall.SysProcAttr{ 32 Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the creating thread in the daemon process dies (https://go.dev/issue/27505) 33 }, 34 }, 35 wait: make(chan error, 1), 36 }, nil 37 } 38 39 // proxyCommand wraps an exec.Cmd to run the userland TCP and UDP 40 // proxies as separate processes. 41 type proxyCommand struct { 42 cmd *exec.Cmd 43 wait chan error 44 } 45 46 func (p *proxyCommand) Start() error { 47 r, w, err := os.Pipe() 48 if err != nil { 49 return fmt.Errorf("proxy unable to open os.Pipe %s", err) 50 } 51 defer r.Close() 52 p.cmd.ExtraFiles = []*os.File{w} 53 54 // As p.cmd.SysProcAttr.Pdeathsig is set, the signal will be sent to the 55 // process when the OS thread on which p.cmd.Start() was executed dies. 56 // If the thread is allowed to be released back into the goroutine 57 // thread pool, the thread could get terminated at any time if a 58 // goroutine gets scheduled onto it which calls runtime.LockOSThread() 59 // and exits without a matching number of runtime.UnlockOSThread() 60 // calls. Ensure that the thread from which Start() is called stays 61 // alive until the proxy or the daemon process exits to prevent the 62 // proxy from getting terminated early. See https://go.dev/issue/27505 63 // for more details. 64 started := make(chan error) 65 go func() { 66 runtime.LockOSThread() 67 defer runtime.UnlockOSThread() 68 err := p.cmd.Start() 69 started <- err 70 if err != nil { 71 return 72 } 73 p.wait <- p.cmd.Wait() 74 }() 75 if err := <-started; err != nil { 76 return err 77 } 78 w.Close() 79 80 errchan := make(chan error, 1) 81 go func() { 82 buf := make([]byte, 2) 83 r.Read(buf) 84 85 if string(buf) != "0\n" { 86 errStr, err := io.ReadAll(r) 87 if err != nil { 88 errchan <- fmt.Errorf("Error reading exit status from userland proxy: %v", err) 89 return 90 } 91 92 errchan <- fmt.Errorf("Error starting userland proxy: %s", errStr) 93 return 94 } 95 errchan <- nil 96 }() 97 98 select { 99 case err := <-errchan: 100 return err 101 case <-time.After(16 * time.Second): 102 return fmt.Errorf("Timed out proxy starting the userland proxy") 103 } 104 } 105 106 func (p *proxyCommand) Stop() error { 107 if p.cmd.Process != nil { 108 if err := p.cmd.Process.Signal(os.Interrupt); err != nil { 109 return err 110 } 111 return <-p.wait 112 } 113 return nil 114 }