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