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