mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			262 lines
		
	
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			262 lines
		
	
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package portmapper
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"net"
 | 
						|
 | 
						|
	"github.com/docker/docker/libnetwork/portallocator"
 | 
						|
	"github.com/ishidawataru/sctp"
 | 
						|
	"github.com/sirupsen/logrus"
 | 
						|
)
 | 
						|
 | 
						|
type mapping struct {
 | 
						|
	proto         string
 | 
						|
	userlandProxy userlandProxy
 | 
						|
	host          net.Addr
 | 
						|
	container     net.Addr
 | 
						|
}
 | 
						|
 | 
						|
// newProxy is used to mock out the proxy server in tests
 | 
						|
var newProxy = newProxyCommand
 | 
						|
 | 
						|
var (
 | 
						|
	// ErrUnknownBackendAddressType refers to an unknown container or unsupported address type
 | 
						|
	ErrUnknownBackendAddressType = errors.New("unknown container address type not supported")
 | 
						|
	// ErrPortMappedForIP refers to a port already mapped to an ip address
 | 
						|
	ErrPortMappedForIP = errors.New("port is already mapped to ip")
 | 
						|
	// ErrPortNotMapped refers to an unmapped port
 | 
						|
	ErrPortNotMapped = errors.New("port is not mapped")
 | 
						|
	// ErrSCTPAddrNoIP refers to a SCTP address without IP address.
 | 
						|
	ErrSCTPAddrNoIP = errors.New("sctp address does not contain any IP address")
 | 
						|
)
 | 
						|
 | 
						|
// New returns a new instance of PortMapper
 | 
						|
func New(proxyPath string) *PortMapper {
 | 
						|
	return NewWithPortAllocator(portallocator.Get(), proxyPath)
 | 
						|
}
 | 
						|
 | 
						|
// NewWithPortAllocator returns a new instance of PortMapper which will use the specified PortAllocator
 | 
						|
func NewWithPortAllocator(allocator *portallocator.PortAllocator, proxyPath string) *PortMapper {
 | 
						|
	return &PortMapper{
 | 
						|
		currentMappings: make(map[string]*mapping),
 | 
						|
		Allocator:       allocator,
 | 
						|
		proxyPath:       proxyPath,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Map maps the specified container transport address to the host's network address and transport port
 | 
						|
func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool) (host net.Addr, err error) {
 | 
						|
	return pm.MapRange(container, hostIP, hostPort, hostPort, useProxy)
 | 
						|
}
 | 
						|
 | 
						|
// MapRange maps the specified container transport address to the host's network address and transport port range
 | 
						|
func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart, hostPortEnd int, useProxy bool) (host net.Addr, err error) {
 | 
						|
	pm.lock.Lock()
 | 
						|
	defer pm.lock.Unlock()
 | 
						|
 | 
						|
	var (
 | 
						|
		m                 *mapping
 | 
						|
		proto             string
 | 
						|
		allocatedHostPort int
 | 
						|
	)
 | 
						|
 | 
						|
	switch t := container.(type) {
 | 
						|
	case *net.TCPAddr:
 | 
						|
		proto = "tcp"
 | 
						|
		if allocatedHostPort, err = pm.Allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		m = &mapping{
 | 
						|
			proto:     proto,
 | 
						|
			host:      &net.TCPAddr{IP: hostIP, Port: allocatedHostPort},
 | 
						|
			container: container,
 | 
						|
		}
 | 
						|
 | 
						|
		if useProxy {
 | 
						|
			m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, t.IP, t.Port, pm.proxyPath)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case *net.UDPAddr:
 | 
						|
		proto = "udp"
 | 
						|
		if allocatedHostPort, err = pm.Allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		m = &mapping{
 | 
						|
			proto:     proto,
 | 
						|
			host:      &net.UDPAddr{IP: hostIP, Port: allocatedHostPort},
 | 
						|
			container: container,
 | 
						|
		}
 | 
						|
 | 
						|
		if useProxy {
 | 
						|
			m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, t.IP, t.Port, pm.proxyPath)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case *sctp.SCTPAddr:
 | 
						|
		proto = "sctp"
 | 
						|
		if allocatedHostPort, err = pm.Allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		m = &mapping{
 | 
						|
			proto:     proto,
 | 
						|
			host:      &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: hostIP}}, Port: allocatedHostPort},
 | 
						|
			container: container,
 | 
						|
		}
 | 
						|
 | 
						|
		if useProxy {
 | 
						|
			sctpAddr := container.(*sctp.SCTPAddr)
 | 
						|
			if len(sctpAddr.IPAddrs) == 0 {
 | 
						|
				return nil, ErrSCTPAddrNoIP
 | 
						|
			}
 | 
						|
			m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, sctpAddr.IPAddrs[0].IP, sctpAddr.Port, pm.proxyPath)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		return nil, ErrUnknownBackendAddressType
 | 
						|
	}
 | 
						|
 | 
						|
	// release the allocated port on any further error during return.
 | 
						|
	defer func() {
 | 
						|
		if err != nil {
 | 
						|
			pm.Allocator.ReleasePort(hostIP, proto, allocatedHostPort)
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	key := getKey(m.host)
 | 
						|
	if _, exists := pm.currentMappings[key]; exists {
 | 
						|
		return nil, ErrPortMappedForIP
 | 
						|
	}
 | 
						|
 | 
						|
	containerIP, containerPort := getIPAndPort(m.container)
 | 
						|
	if err := pm.AppendForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	cleanup := func() error {
 | 
						|
		// need to undo the iptables rules before we return
 | 
						|
		m.userlandProxy.Stop()
 | 
						|
		pm.DeleteForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
 | 
						|
		if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if err := m.userlandProxy.Start(); err != nil {
 | 
						|
		if err := cleanup(); err != nil {
 | 
						|
			return nil, fmt.Errorf("Error during port allocation cleanup: %v", err)
 | 
						|
		}
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	pm.currentMappings[key] = m
 | 
						|
	return m.host, nil
 | 
						|
}
 | 
						|
 | 
						|
// Unmap removes stored mapping for the specified host transport address
 | 
						|
func (pm *PortMapper) Unmap(host net.Addr) error {
 | 
						|
	pm.lock.Lock()
 | 
						|
	defer pm.lock.Unlock()
 | 
						|
 | 
						|
	key := getKey(host)
 | 
						|
	data, exists := pm.currentMappings[key]
 | 
						|
	if !exists {
 | 
						|
		return ErrPortNotMapped
 | 
						|
	}
 | 
						|
 | 
						|
	if data.userlandProxy != nil {
 | 
						|
		data.userlandProxy.Stop()
 | 
						|
	}
 | 
						|
 | 
						|
	delete(pm.currentMappings, key)
 | 
						|
 | 
						|
	containerIP, containerPort := getIPAndPort(data.container)
 | 
						|
	hostIP, hostPort := getIPAndPort(data.host)
 | 
						|
	if err := pm.DeleteForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
 | 
						|
		logrus.Errorf("Error on iptables delete: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	switch a := host.(type) {
 | 
						|
	case *net.TCPAddr:
 | 
						|
		return pm.Allocator.ReleasePort(a.IP, "tcp", a.Port)
 | 
						|
	case *net.UDPAddr:
 | 
						|
		return pm.Allocator.ReleasePort(a.IP, "udp", a.Port)
 | 
						|
	case *sctp.SCTPAddr:
 | 
						|
		if len(a.IPAddrs) == 0 {
 | 
						|
			return ErrSCTPAddrNoIP
 | 
						|
		}
 | 
						|
		return pm.Allocator.ReleasePort(a.IPAddrs[0].IP, "sctp", a.Port)
 | 
						|
	}
 | 
						|
	return ErrUnknownBackendAddressType
 | 
						|
}
 | 
						|
 | 
						|
// ReMapAll re-applies all port mappings
 | 
						|
func (pm *PortMapper) ReMapAll() {
 | 
						|
	pm.lock.Lock()
 | 
						|
	defer pm.lock.Unlock()
 | 
						|
	logrus.Debugln("Re-applying all port mappings.")
 | 
						|
	for _, data := range pm.currentMappings {
 | 
						|
		containerIP, containerPort := getIPAndPort(data.container)
 | 
						|
		hostIP, hostPort := getIPAndPort(data.host)
 | 
						|
		if err := pm.AppendForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
 | 
						|
			logrus.Errorf("Error on iptables add: %s", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func getKey(a net.Addr) string {
 | 
						|
	switch t := a.(type) {
 | 
						|
	case *net.TCPAddr:
 | 
						|
		return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp")
 | 
						|
	case *net.UDPAddr:
 | 
						|
		return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp")
 | 
						|
	case *sctp.SCTPAddr:
 | 
						|
		if len(t.IPAddrs) == 0 {
 | 
						|
			logrus.Error(ErrSCTPAddrNoIP)
 | 
						|
			return ""
 | 
						|
		}
 | 
						|
		return fmt.Sprintf("%s:%d/%s", t.IPAddrs[0].IP.String(), t.Port, "sctp")
 | 
						|
	}
 | 
						|
	return ""
 | 
						|
}
 | 
						|
 | 
						|
func getIPAndPort(a net.Addr) (net.IP, int) {
 | 
						|
	switch t := a.(type) {
 | 
						|
	case *net.TCPAddr:
 | 
						|
		return t.IP, t.Port
 | 
						|
	case *net.UDPAddr:
 | 
						|
		return t.IP, t.Port
 | 
						|
	case *sctp.SCTPAddr:
 | 
						|
		if len(t.IPAddrs) == 0 {
 | 
						|
			logrus.Error(ErrSCTPAddrNoIP)
 | 
						|
			return nil, 0
 | 
						|
		}
 | 
						|
		return t.IPAddrs[0].IP, t.Port
 | 
						|
	}
 | 
						|
	return nil, 0
 | 
						|
}
 |