github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/libnetwork/portmapper/mapper.go (about)

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