github.com/squaremo/docker@v1.3.2-0.20150516120342-42cfc9554972/daemon/networkdriver/portmapper/mapper.go (about)

     1  package portmapper
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"sync"
     8  
     9  	"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, useProxy bool) (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  	)
    63  
    64  	switch container.(type) {
    65  	case *net.TCPAddr:
    66  		proto = "tcp"
    67  		if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
    68  			return nil, err
    69  		}
    70  
    71  		m = &mapping{
    72  			proto:     proto,
    73  			host:      &net.TCPAddr{IP: hostIP, Port: allocatedHostPort},
    74  			container: container,
    75  		}
    76  
    77  		if useProxy {
    78  			m.userlandProxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
    79  		}
    80  	case *net.UDPAddr:
    81  		proto = "udp"
    82  		if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
    83  			return nil, err
    84  		}
    85  
    86  		m = &mapping{
    87  			proto:     proto,
    88  			host:      &net.UDPAddr{IP: hostIP, Port: allocatedHostPort},
    89  			container: container,
    90  		}
    91  
    92  		if useProxy {
    93  			m.userlandProxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port)
    94  		}
    95  	default:
    96  		return nil, ErrUnknownBackendAddressType
    97  	}
    98  
    99  	// release the allocated port on any further error during return.
   100  	defer func() {
   101  		if err != nil {
   102  			pm.Allocator.ReleasePort(hostIP, proto, allocatedHostPort)
   103  		}
   104  	}()
   105  
   106  	key := getKey(m.host)
   107  	if _, exists := pm.currentMappings[key]; exists {
   108  		return nil, ErrPortMappedForIP
   109  	}
   110  
   111  	containerIP, containerPort := getIPAndPort(m.container)
   112  	if err := pm.forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	cleanup := func() error {
   117  		// need to undo the iptables rules before we return
   118  		if m.userlandProxy != nil {
   119  			m.userlandProxy.Stop()
   120  		}
   121  		pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
   122  		if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
   123  			return err
   124  		}
   125  
   126  		return nil
   127  	}
   128  
   129  	if m.userlandProxy != nil {
   130  		if err := m.userlandProxy.Start(); err != nil {
   131  			if err := cleanup(); err != nil {
   132  				return nil, fmt.Errorf("Error during port allocation cleanup: %v", err)
   133  			}
   134  			return nil, err
   135  		}
   136  	}
   137  
   138  	pm.currentMappings[key] = m
   139  	return m.host, nil
   140  }
   141  
   142  // re-apply all port mappings
   143  func (pm *PortMapper) ReMapAll() {
   144  	logrus.Debugln("Re-applying all port mappings.")
   145  	for _, data := range pm.currentMappings {
   146  		containerIP, containerPort := getIPAndPort(data.container)
   147  		hostIP, hostPort := getIPAndPort(data.host)
   148  		if err := pm.forward(iptables.Append, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
   149  			logrus.Errorf("Error on iptables add: %s", err)
   150  		}
   151  	}
   152  }
   153  
   154  func (pm *PortMapper) Unmap(host net.Addr) error {
   155  	pm.lock.Lock()
   156  	defer pm.lock.Unlock()
   157  
   158  	key := getKey(host)
   159  	data, exists := pm.currentMappings[key]
   160  	if !exists {
   161  		return ErrPortNotMapped
   162  	}
   163  
   164  	if data.userlandProxy != nil {
   165  		data.userlandProxy.Stop()
   166  	}
   167  
   168  	delete(pm.currentMappings, key)
   169  
   170  	containerIP, containerPort := getIPAndPort(data.container)
   171  	hostIP, hostPort := getIPAndPort(data.host)
   172  	if err := pm.forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
   173  		logrus.Errorf("Error on iptables delete: %s", err)
   174  	}
   175  
   176  	switch a := host.(type) {
   177  	case *net.TCPAddr:
   178  		return pm.Allocator.ReleasePort(a.IP, "tcp", a.Port)
   179  	case *net.UDPAddr:
   180  		return pm.Allocator.ReleasePort(a.IP, "udp", a.Port)
   181  	}
   182  	return nil
   183  }
   184  
   185  func getKey(a net.Addr) string {
   186  	switch t := a.(type) {
   187  	case *net.TCPAddr:
   188  		return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp")
   189  	case *net.UDPAddr:
   190  		return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp")
   191  	}
   192  	return ""
   193  }
   194  
   195  func getIPAndPort(a net.Addr) (net.IP, int) {
   196  	switch t := a.(type) {
   197  	case *net.TCPAddr:
   198  		return t.IP, t.Port
   199  	case *net.UDPAddr:
   200  		return t.IP, t.Port
   201  	}
   202  	return nil, 0
   203  }
   204  
   205  func (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
   206  	if pm.chain == nil {
   207  		return nil
   208  	}
   209  	return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort)
   210  }