github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/libnetwork/portmapper/mapper.go (about)

     1  package portmapper
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  
     9  	"github.com/Prakhar-Agarwal-byte/moby/libnetwork/portallocator"
    10  	"github.com/containerd/log"
    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, err 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, err 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  		if allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd); err != nil {
    69  			return nil, err
    70  		}
    71  
    72  		m = &mapping{
    73  			proto:     proto,
    74  			host:      &net.TCPAddr{IP: hostIP, Port: allocatedHostPort},
    75  			container: container,
    76  		}
    77  
    78  		if useProxy {
    79  			m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, t.IP, t.Port, pm.proxyPath)
    80  			if err != nil {
    81  				return nil, err
    82  			}
    83  		} else {
    84  			m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort)
    85  			if err != nil {
    86  				return nil, err
    87  			}
    88  		}
    89  	case *net.UDPAddr:
    90  		proto = "udp"
    91  		if allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd); err != nil {
    92  			return nil, err
    93  		}
    94  
    95  		m = &mapping{
    96  			proto:     proto,
    97  			host:      &net.UDPAddr{IP: hostIP, Port: allocatedHostPort},
    98  			container: container,
    99  		}
   100  
   101  		if useProxy {
   102  			m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, t.IP, t.Port, pm.proxyPath)
   103  			if err != nil {
   104  				return nil, err
   105  			}
   106  		} else {
   107  			m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort)
   108  			if err != nil {
   109  				return nil, err
   110  			}
   111  		}
   112  	case *sctp.SCTPAddr:
   113  		proto = "sctp"
   114  		if allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd); err != nil {
   115  			return nil, err
   116  		}
   117  
   118  		m = &mapping{
   119  			proto:     proto,
   120  			host:      &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: hostIP}}, Port: allocatedHostPort},
   121  			container: container,
   122  		}
   123  
   124  		if useProxy {
   125  			sctpAddr := container.(*sctp.SCTPAddr)
   126  			if len(sctpAddr.IPAddrs) == 0 {
   127  				return nil, ErrSCTPAddrNoIP
   128  			}
   129  			m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, sctpAddr.IPAddrs[0].IP, sctpAddr.Port, pm.proxyPath)
   130  			if err != nil {
   131  				return nil, err
   132  			}
   133  		} else {
   134  			m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort)
   135  			if err != nil {
   136  				return nil, err
   137  			}
   138  		}
   139  	default:
   140  		return nil, ErrUnknownBackendAddressType
   141  	}
   142  
   143  	// release the allocated port on any further error during return.
   144  	defer func() {
   145  		if err != nil {
   146  			pm.allocator.ReleasePort(hostIP, proto, allocatedHostPort)
   147  		}
   148  	}()
   149  
   150  	key := getKey(m.host)
   151  	if _, exists := pm.currentMappings[key]; exists {
   152  		return nil, ErrPortMappedForIP
   153  	}
   154  
   155  	containerIP, containerPort := getIPAndPort(m.container)
   156  	if err := pm.AppendForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	cleanup := func() error {
   161  		// need to undo the iptables rules before we return
   162  		m.userlandProxy.Stop()
   163  		pm.DeleteForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
   164  		if err := pm.allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
   165  			return err
   166  		}
   167  
   168  		return nil
   169  	}
   170  
   171  	if err := m.userlandProxy.Start(); err != nil {
   172  		if err := cleanup(); err != nil {
   173  			return nil, fmt.Errorf("Error during port allocation cleanup: %v", err)
   174  		}
   175  		return nil, err
   176  	}
   177  
   178  	pm.currentMappings[key] = m
   179  	return m.host, nil
   180  }
   181  
   182  // Unmap removes stored mapping for the specified host transport address
   183  func (pm *PortMapper) Unmap(host net.Addr) error {
   184  	pm.lock.Lock()
   185  	defer pm.lock.Unlock()
   186  
   187  	key := getKey(host)
   188  	data, exists := pm.currentMappings[key]
   189  	if !exists {
   190  		return ErrPortNotMapped
   191  	}
   192  
   193  	if data.userlandProxy != nil {
   194  		data.userlandProxy.Stop()
   195  	}
   196  
   197  	delete(pm.currentMappings, key)
   198  
   199  	containerIP, containerPort := getIPAndPort(data.container)
   200  	hostIP, hostPort := getIPAndPort(data.host)
   201  	if err := pm.DeleteForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
   202  		log.G(context.TODO()).Errorf("Error on iptables delete: %s", err)
   203  	}
   204  
   205  	switch a := host.(type) {
   206  	case *net.TCPAddr:
   207  		return pm.allocator.ReleasePort(a.IP, "tcp", a.Port)
   208  	case *net.UDPAddr:
   209  		return pm.allocator.ReleasePort(a.IP, "udp", a.Port)
   210  	case *sctp.SCTPAddr:
   211  		if len(a.IPAddrs) == 0 {
   212  			return ErrSCTPAddrNoIP
   213  		}
   214  		return pm.allocator.ReleasePort(a.IPAddrs[0].IP, "sctp", a.Port)
   215  	}
   216  	return ErrUnknownBackendAddressType
   217  }
   218  
   219  // ReMapAll re-applies all port mappings
   220  func (pm *PortMapper) ReMapAll() {
   221  	pm.lock.Lock()
   222  	defer pm.lock.Unlock()
   223  	log.G(context.TODO()).Debugln("Re-applying all port mappings.")
   224  	for _, data := range pm.currentMappings {
   225  		containerIP, containerPort := getIPAndPort(data.container)
   226  		hostIP, hostPort := getIPAndPort(data.host)
   227  		if err := pm.AppendForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
   228  			log.G(context.TODO()).Errorf("Error on iptables add: %s", err)
   229  		}
   230  	}
   231  }
   232  
   233  func getKey(a net.Addr) string {
   234  	switch t := a.(type) {
   235  	case *net.TCPAddr:
   236  		return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp")
   237  	case *net.UDPAddr:
   238  		return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp")
   239  	case *sctp.SCTPAddr:
   240  		if len(t.IPAddrs) == 0 {
   241  			log.G(context.TODO()).Error(ErrSCTPAddrNoIP)
   242  			return ""
   243  		}
   244  		return fmt.Sprintf("%s:%d/%s", t.IPAddrs[0].IP.String(), t.Port, "sctp")
   245  	}
   246  	return ""
   247  }
   248  
   249  func getIPAndPort(a net.Addr) (net.IP, int) {
   250  	switch t := a.(type) {
   251  	case *net.TCPAddr:
   252  		return t.IP, t.Port
   253  	case *net.UDPAddr:
   254  		return t.IP, t.Port
   255  	case *sctp.SCTPAddr:
   256  		if len(t.IPAddrs) == 0 {
   257  			log.G(context.TODO()).Error(ErrSCTPAddrNoIP)
   258  			return nil, 0
   259  		}
   260  		return t.IPAddrs[0].IP, t.Port
   261  	}
   262  	return nil, 0
   263  }