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

     1  package portmapper
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"sync"
     8  
     9  	log "github.com/Sirupsen/logrus"
    10  	"github.com/docker/docker/daemon/networkdriver/portallocator"
    11  	"github.com/docker/docker/pkg/iptables"
    12  )
    13  
    14  type mapping struct {
    15  	proto         string
    16  	userlandProxy UserlandProxy
    17  	host          net.Addr
    18  	container     net.Addr
    19  }
    20  
    21  var (
    22  	chain *iptables.Chain
    23  	lock  sync.Mutex
    24  
    25  	// udp:ip:port
    26  	currentMappings = make(map[string]*mapping)
    27  
    28  	NewProxy = NewProxyCommand
    29  )
    30  
    31  var (
    32  	ErrUnknownBackendAddressType = errors.New("unknown container address type not supported")
    33  	ErrPortMappedForIP           = errors.New("port is already mapped to ip")
    34  	ErrPortNotMapped             = errors.New("port is not mapped")
    35  )
    36  
    37  func SetIptablesChain(c *iptables.Chain) {
    38  	chain = c
    39  }
    40  
    41  func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err error) {
    42  	lock.Lock()
    43  	defer lock.Unlock()
    44  
    45  	var (
    46  		m                 *mapping
    47  		proto             string
    48  		allocatedHostPort int
    49  		proxy             UserlandProxy
    50  	)
    51  
    52  	switch container.(type) {
    53  	case *net.TCPAddr:
    54  		proto = "tcp"
    55  		if allocatedHostPort, err = portallocator.RequestPort(hostIP, proto, hostPort); err != nil {
    56  			return nil, err
    57  		}
    58  
    59  		m = &mapping{
    60  			proto:     proto,
    61  			host:      &net.TCPAddr{IP: hostIP, Port: allocatedHostPort},
    62  			container: container,
    63  		}
    64  
    65  		proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
    66  	case *net.UDPAddr:
    67  		proto = "udp"
    68  		if allocatedHostPort, err = portallocator.RequestPort(hostIP, proto, hostPort); err != nil {
    69  			return nil, err
    70  		}
    71  
    72  		m = &mapping{
    73  			proto:     proto,
    74  			host:      &net.UDPAddr{IP: hostIP, Port: allocatedHostPort},
    75  			container: container,
    76  		}
    77  
    78  		proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port)
    79  	default:
    80  		return nil, ErrUnknownBackendAddressType
    81  	}
    82  
    83  	// release the allocated port on any further error during return.
    84  	defer func() {
    85  		if err != nil {
    86  			portallocator.ReleasePort(hostIP, proto, allocatedHostPort)
    87  		}
    88  	}()
    89  
    90  	key := getKey(m.host)
    91  	if _, exists := currentMappings[key]; exists {
    92  		return nil, ErrPortMappedForIP
    93  	}
    94  
    95  	containerIP, containerPort := getIPAndPort(m.container)
    96  	if err := forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	cleanup := func() error {
   101  		// need to undo the iptables rules before we return
   102  		proxy.Stop()
   103  		forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
   104  		if err := portallocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
   105  			return err
   106  		}
   107  
   108  		return nil
   109  	}
   110  
   111  	if err := proxy.Start(); err != nil {
   112  		if err := cleanup(); err != nil {
   113  			return nil, fmt.Errorf("Error during port allocation cleanup: %v", err)
   114  		}
   115  		return nil, err
   116  	}
   117  	m.userlandProxy = proxy
   118  	currentMappings[key] = m
   119  	return m.host, nil
   120  }
   121  
   122  func Unmap(host net.Addr) error {
   123  	lock.Lock()
   124  	defer lock.Unlock()
   125  
   126  	key := getKey(host)
   127  	data, exists := currentMappings[key]
   128  	if !exists {
   129  		return ErrPortNotMapped
   130  	}
   131  
   132  	data.userlandProxy.Stop()
   133  
   134  	delete(currentMappings, key)
   135  
   136  	containerIP, containerPort := getIPAndPort(data.container)
   137  	hostIP, hostPort := getIPAndPort(data.host)
   138  	if err := forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
   139  		log.Errorf("Error on iptables delete: %s", err)
   140  	}
   141  
   142  	switch a := host.(type) {
   143  	case *net.TCPAddr:
   144  		return portallocator.ReleasePort(a.IP, "tcp", a.Port)
   145  	case *net.UDPAddr:
   146  		return portallocator.ReleasePort(a.IP, "udp", a.Port)
   147  	}
   148  	return nil
   149  }
   150  
   151  func getKey(a net.Addr) string {
   152  	switch t := a.(type) {
   153  	case *net.TCPAddr:
   154  		return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp")
   155  	case *net.UDPAddr:
   156  		return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp")
   157  	}
   158  	return ""
   159  }
   160  
   161  func getIPAndPort(a net.Addr) (net.IP, int) {
   162  	switch t := a.(type) {
   163  	case *net.TCPAddr:
   164  		return t.IP, t.Port
   165  	case *net.UDPAddr:
   166  		return t.IP, t.Port
   167  	}
   168  	return nil, 0
   169  }
   170  
   171  func forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
   172  	if chain == nil {
   173  		return nil
   174  	}
   175  	return chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort)
   176  }