github.com/daaku/docker@v1.5.0/daemon/networkdriver/portmapper/proxy.go (about)

     1  package portmapper
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"net"
     9  	"os"
    10  	"os/exec"
    11  	"os/signal"
    12  	"strconv"
    13  	"syscall"
    14  	"time"
    15  
    16  	"github.com/docker/docker/pkg/proxy"
    17  	"github.com/docker/docker/pkg/reexec"
    18  )
    19  
    20  const userlandProxyCommandName = "docker-proxy"
    21  
    22  func init() {
    23  	reexec.Register(userlandProxyCommandName, execProxy)
    24  }
    25  
    26  type UserlandProxy interface {
    27  	Start() error
    28  	Stop() error
    29  }
    30  
    31  // proxyCommand wraps an exec.Cmd to run the userland TCP and UDP
    32  // proxies as separate processes.
    33  type proxyCommand struct {
    34  	cmd *exec.Cmd
    35  }
    36  
    37  // execProxy is the reexec function that is registered to start the userland proxies
    38  func execProxy() {
    39  	f := os.NewFile(3, "signal-parent")
    40  	host, container := parseHostContainerAddrs()
    41  
    42  	p, err := proxy.NewProxy(host, container)
    43  	if err != nil {
    44  		fmt.Fprintf(f, "1\n%s", err)
    45  		f.Close()
    46  		os.Exit(1)
    47  	}
    48  	go handleStopSignals(p)
    49  	fmt.Fprint(f, "0\n")
    50  	f.Close()
    51  
    52  	// Run will block until the proxy stops
    53  	p.Run()
    54  }
    55  
    56  // parseHostContainerAddrs parses the flags passed on reexec to create the TCP or UDP
    57  // net.Addrs to map the host and container ports
    58  func parseHostContainerAddrs() (host net.Addr, container net.Addr) {
    59  	var (
    60  		proto         = flag.String("proto", "tcp", "proxy protocol")
    61  		hostIP        = flag.String("host-ip", "", "host ip")
    62  		hostPort      = flag.Int("host-port", -1, "host port")
    63  		containerIP   = flag.String("container-ip", "", "container ip")
    64  		containerPort = flag.Int("container-port", -1, "container port")
    65  	)
    66  
    67  	flag.Parse()
    68  
    69  	switch *proto {
    70  	case "tcp":
    71  		host = &net.TCPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort}
    72  		container = &net.TCPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort}
    73  	case "udp":
    74  		host = &net.UDPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort}
    75  		container = &net.UDPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort}
    76  	default:
    77  		log.Fatalf("unsupported protocol %s", *proto)
    78  	}
    79  
    80  	return host, container
    81  }
    82  
    83  func handleStopSignals(p proxy.Proxy) {
    84  	s := make(chan os.Signal, 10)
    85  	signal.Notify(s, os.Interrupt, syscall.SIGTERM, syscall.SIGSTOP)
    86  
    87  	for _ = range s {
    88  		p.Close()
    89  
    90  		os.Exit(0)
    91  	}
    92  }
    93  
    94  func NewProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int) UserlandProxy {
    95  	args := []string{
    96  		userlandProxyCommandName,
    97  		"-proto", proto,
    98  		"-host-ip", hostIP.String(),
    99  		"-host-port", strconv.Itoa(hostPort),
   100  		"-container-ip", containerIP.String(),
   101  		"-container-port", strconv.Itoa(containerPort),
   102  	}
   103  
   104  	return &proxyCommand{
   105  		cmd: &exec.Cmd{
   106  			Path: reexec.Self(),
   107  			Args: args,
   108  			SysProcAttr: &syscall.SysProcAttr{
   109  				Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the daemon process dies
   110  			},
   111  		},
   112  	}
   113  }
   114  
   115  func (p *proxyCommand) Start() error {
   116  	r, w, err := os.Pipe()
   117  	if err != nil {
   118  		return fmt.Errorf("proxy unable to open os.Pipe %s", err)
   119  	}
   120  	defer r.Close()
   121  	p.cmd.ExtraFiles = []*os.File{w}
   122  	if err := p.cmd.Start(); err != nil {
   123  		return err
   124  	}
   125  	w.Close()
   126  
   127  	errchan := make(chan error, 1)
   128  	go func() {
   129  		buf := make([]byte, 2)
   130  		r.Read(buf)
   131  
   132  		if string(buf) != "0\n" {
   133  			errStr, err := ioutil.ReadAll(r)
   134  			if err != nil {
   135  				errchan <- fmt.Errorf("Error reading exit status from userland proxy: %v", err)
   136  				return
   137  			}
   138  
   139  			errchan <- fmt.Errorf("Error starting userland proxy: %s", errStr)
   140  			return
   141  		}
   142  		errchan <- nil
   143  	}()
   144  
   145  	select {
   146  	case err := <-errchan:
   147  		return err
   148  	case <-time.After(16 * time.Second):
   149  		return fmt.Errorf("Timed out proxy starting the userland proxy")
   150  	}
   151  }
   152  
   153  func (p *proxyCommand) Stop() error {
   154  	if p.cmd.Process != nil {
   155  		if err := p.cmd.Process.Signal(os.Interrupt); err != nil {
   156  			return err
   157  		}
   158  		return p.cmd.Wait()
   159  	}
   160  	return nil
   161  }