123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- package portmapper
- import (
- "context"
- "errors"
- "fmt"
- "net"
- "github.com/containerd/log"
- "github.com/docker/docker/libnetwork/portallocator"
- "github.com/ishidawataru/sctp"
- )
- 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() *PortMapper {
- return NewWithPortAllocator(portallocator.Get(), "")
- }
- // 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 {
- log.G(context.TODO()).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()
- log.G(context.TODO()).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 {
- log.G(context.TODO()).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 {
- log.G(context.TODO()).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 {
- log.G(context.TODO()).Error(ErrSCTPAddrNoIP)
- return nil, 0
- }
- return t.IPAddrs[0].IP, t.Port
- }
- return nil, 0
- }
|