github.com/adityamillind98/moby@v23.0.0-rc.4+incompatible/libnetwork/portmapper/mapper.go (about)

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