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  }