github.com/rish1988/moby@v25.0.2+incompatible/libnetwork/portmapper/mapper.go (about)

     1  package portmapper
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  
     9  	"github.com/containerd/log"
    10  	"github.com/docker/docker/libnetwork/portallocator"
    11  	"github.com/ishidawataru/sctp"
    12  )
    13  
    14  type mapping struct {
    15  	proto         string
    16  	userlandProxy userlandProxy
    17  	host          net.Addr
    18  	container     net.Addr
    19  }
    20  
    21  // newProxy is used to mock out the proxy server in tests
    22  var newProxy = newProxyCommand
    23  
    24  var (
    25  	// ErrUnknownBackendAddressType refers to an unknown container or unsupported address type
    26  	ErrUnknownBackendAddressType = errors.New("unknown container address type not supported")
    27  	// ErrPortMappedForIP refers to a port already mapped to an ip address
    28  	ErrPortMappedForIP = errors.New("port is already mapped to ip")
    29  	// ErrPortNotMapped refers to an unmapped port
    30  	ErrPortNotMapped = errors.New("port is not mapped")
    31  	// ErrSCTPAddrNoIP refers to a SCTP address without IP address.
    32  	ErrSCTPAddrNoIP = errors.New("sctp address does not contain any IP address")
    33  )
    34  
    35  // New returns a new instance of PortMapper
    36  func New() *PortMapper {
    37  	return NewWithPortAllocator(portallocator.Get(), "")
    38  }
    39  
    40  // NewWithPortAllocator returns a new instance of PortMapper which will use the specified PortAllocator
    41  func NewWithPortAllocator(allocator *portallocator.PortAllocator, proxyPath string) *PortMapper {
    42  	return &PortMapper{
    43  		currentMappings: make(map[string]*mapping),
    44  		allocator:       allocator,
    45  		proxyPath:       proxyPath,
    46  	}
    47  }
    48  
    49  // Map maps the specified container transport address to the host's network address and transport port
    50  func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool) (host net.Addr, _ error) {
    51  	return pm.MapRange(container, hostIP, hostPort, hostPort, useProxy)
    52  }
    53  
    54  // MapRange maps the specified container transport address to the host's network address and transport port range
    55  func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart, hostPortEnd int, useProxy bool) (host net.Addr, retErr error) {
    56  	pm.lock.Lock()
    57  	defer pm.lock.Unlock()
    58  
    59  	var (
    60  		m                 *mapping
    61  		proto             string
    62  		allocatedHostPort int
    63  	)
    64  
    65  	switch t := container.(type) {
    66  	case *net.TCPAddr:
    67  		proto = "tcp"
    68  
    69  		var err error
    70  		allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd)
    71  		if err != nil {
    72  			return nil, err
    73  		}
    74  		defer func() {
    75  			if retErr != nil {
    76  				pm.allocator.ReleasePort(hostIP, proto, allocatedHostPort)
    77  			}
    78  		}()
    79  
    80  		m = &mapping{
    81  			proto:     proto,
    82  			host:      &net.TCPAddr{IP: hostIP, Port: allocatedHostPort},
    83  			container: container,
    84  		}
    85  
    86  		if useProxy {
    87  			m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, t.IP, t.Port, pm.proxyPath)
    88  			if err != nil {
    89  				return nil, err
    90  			}
    91  		} else {
    92  			m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort)
    93  			if err != nil {
    94  				return nil, err
    95  			}
    96  		}
    97  	case *net.UDPAddr:
    98  		proto = "udp"
    99  
   100  		var err error
   101  		allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd)
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  		defer func() {
   106  			if retErr != nil {
   107  				pm.allocator.ReleasePort(hostIP, proto, allocatedHostPort)
   108  			}
   109  		}()
   110  
   111  		m = &mapping{
   112  			proto:     proto,
   113  			host:      &net.UDPAddr{IP: hostIP, Port: allocatedHostPort},
   114  			container: container,
   115  		}
   116  
   117  		if useProxy {
   118  			m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, t.IP, t.Port, pm.proxyPath)
   119  			if err != nil {
   120  				return nil, err
   121  			}
   122  		} else {
   123  			m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort)
   124  			if err != nil {
   125  				return nil, err
   126  			}
   127  		}
   128  	case *sctp.SCTPAddr:
   129  		proto = "sctp"
   130  
   131  		var err error
   132  		allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd)
   133  		if err != nil {
   134  			return nil, err
   135  		}
   136  		defer func() {
   137  			if retErr != nil {
   138  				pm.allocator.ReleasePort(hostIP, proto, allocatedHostPort)
   139  			}
   140  		}()
   141  
   142  		m = &mapping{
   143  			proto:     proto,
   144  			host:      &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: hostIP}}, Port: allocatedHostPort},
   145  			container: container,
   146  		}
   147  
   148  		if useProxy {
   149  			sctpAddr := container.(*sctp.SCTPAddr)
   150  			if len(sctpAddr.IPAddrs) == 0 {
   151  				return nil, ErrSCTPAddrNoIP
   152  			}
   153  			m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, sctpAddr.IPAddrs[0].IP, sctpAddr.Port, pm.proxyPath)
   154  			if err != nil {
   155  				return nil, err
   156  			}
   157  		} else {
   158  			m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort)
   159  			if err != nil {
   160  				return nil, err
   161  			}
   162  		}
   163  	default:
   164  		return nil, ErrUnknownBackendAddressType
   165  	}
   166  
   167  	key := getKey(m.host)
   168  	if _, exists := pm.currentMappings[key]; exists {
   169  		return nil, ErrPortMappedForIP
   170  	}
   171  
   172  	containerIP, containerPort := getIPAndPort(m.container)
   173  	if err := pm.AppendForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	if err := m.userlandProxy.Start(); err != nil {
   178  		// FIXME(thaJeztah): both stopping the proxy and deleting iptables rules can produce an error, and both are not currently handled.
   179  		m.userlandProxy.Stop()
   180  		// need to undo the iptables rules before we return
   181  		pm.DeleteForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
   182  		return nil, err
   183  	}
   184  
   185  	pm.currentMappings[key] = m
   186  	return m.host, nil
   187  }
   188  
   189  // Unmap removes stored mapping for the specified host transport address
   190  func (pm *PortMapper) Unmap(host net.Addr) error {
   191  	pm.lock.Lock()
   192  	defer pm.lock.Unlock()
   193  
   194  	key := getKey(host)
   195  	data, exists := pm.currentMappings[key]
   196  	if !exists {
   197  		return ErrPortNotMapped
   198  	}
   199  
   200  	if data.userlandProxy != nil {
   201  		data.userlandProxy.Stop()
   202  	}
   203  
   204  	delete(pm.currentMappings, key)
   205  
   206  	containerIP, containerPort := getIPAndPort(data.container)
   207  	hostIP, hostPort := getIPAndPort(data.host)
   208  	if err := pm.DeleteForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
   209  		log.G(context.TODO()).Errorf("Error on iptables delete: %s", err)
   210  	}
   211  
   212  	switch a := host.(type) {
   213  	case *net.TCPAddr:
   214  		pm.allocator.ReleasePort(a.IP, "tcp", a.Port)
   215  	case *net.UDPAddr:
   216  		pm.allocator.ReleasePort(a.IP, "udp", a.Port)
   217  	case *sctp.SCTPAddr:
   218  		if len(a.IPAddrs) == 0 {
   219  			return ErrSCTPAddrNoIP
   220  		}
   221  		pm.allocator.ReleasePort(a.IPAddrs[0].IP, "sctp", a.Port)
   222  	default:
   223  		return ErrUnknownBackendAddressType
   224  	}
   225  
   226  	return nil
   227  }
   228  
   229  // ReMapAll re-applies all port mappings
   230  func (pm *PortMapper) ReMapAll() {
   231  	pm.lock.Lock()
   232  	defer pm.lock.Unlock()
   233  	log.G(context.TODO()).Debugln("Re-applying all port mappings.")
   234  	for _, data := range pm.currentMappings {
   235  		containerIP, containerPort := getIPAndPort(data.container)
   236  		hostIP, hostPort := getIPAndPort(data.host)
   237  		if err := pm.AppendForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
   238  			log.G(context.TODO()).Errorf("Error on iptables add: %s", err)
   239  		}
   240  	}
   241  }
   242  
   243  func getKey(a net.Addr) string {
   244  	switch t := a.(type) {
   245  	case *net.TCPAddr:
   246  		return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp")
   247  	case *net.UDPAddr:
   248  		return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp")
   249  	case *sctp.SCTPAddr:
   250  		if len(t.IPAddrs) == 0 {
   251  			log.G(context.TODO()).Error(ErrSCTPAddrNoIP)
   252  			return ""
   253  		}
   254  		return fmt.Sprintf("%s:%d/%s", t.IPAddrs[0].IP.String(), t.Port, "sctp")
   255  	}
   256  	return ""
   257  }
   258  
   259  func getIPAndPort(a net.Addr) (net.IP, int) {
   260  	switch t := a.(type) {
   261  	case *net.TCPAddr:
   262  		return t.IP, t.Port
   263  	case *net.UDPAddr:
   264  		return t.IP, t.Port
   265  	case *sctp.SCTPAddr:
   266  		if len(t.IPAddrs) == 0 {
   267  			log.G(context.TODO()).Error(ErrSCTPAddrNoIP)
   268  			return nil, 0
   269  		}
   270  		return t.IPAddrs[0].IP, t.Port
   271  	}
   272  	return nil, 0
   273  }