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