github.com/FlowerWrong/netstack@v0.0.0-20191009141956-e5848263af28/tcpip/ports/ports.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package ports provides PortManager that manages allocating, reserving and releasing ports.
    16  package ports
    17  
    18  import (
    19  	"math"
    20  	"math/rand"
    21  	"sync"
    22  	"sync/atomic"
    23  
    24  	"github.com/FlowerWrong/netstack/tcpip"
    25  )
    26  
    27  const (
    28  	// FirstEphemeral is the first ephemeral port.
    29  	FirstEphemeral = 16000
    30  
    31  	// numEphemeralPorts it the mnumber of available ephemeral ports to
    32  	// Netstack.
    33  	numEphemeralPorts = math.MaxUint16 - FirstEphemeral + 1
    34  
    35  	anyIPAddress tcpip.Address = ""
    36  )
    37  
    38  type portDescriptor struct {
    39  	network   tcpip.NetworkProtocolNumber
    40  	transport tcpip.TransportProtocolNumber
    41  	port      uint16
    42  }
    43  
    44  // PortManager manages allocating, reserving and releasing ports.
    45  type PortManager struct {
    46  	mu             sync.RWMutex
    47  	allocatedPorts map[portDescriptor]bindAddresses
    48  
    49  	// hint is used to pick ports ephemeral ports in a stable order for
    50  	// a given port offset.
    51  	//
    52  	// hint must be accessed using the portHint/incPortHint helpers.
    53  	// TODO(gvisor.dev/issue/940): S/R this field.
    54  	hint uint32
    55  }
    56  
    57  type portNode struct {
    58  	reuse bool
    59  	refs  int
    60  }
    61  
    62  // deviceNode is never empty. When it has no elements, it is removed from the
    63  // map that references it.
    64  type deviceNode map[tcpip.NICID]portNode
    65  
    66  // isAvailable checks whether binding is possible by device. If not binding to a
    67  // device, check against all portNodes. If binding to a specific device, check
    68  // against the unspecified device and the provided device.
    69  func (d deviceNode) isAvailable(reuse bool, bindToDevice tcpip.NICID) bool {
    70  	if bindToDevice == 0 {
    71  		// Trying to binding all devices.
    72  		if !reuse {
    73  			// Can't bind because the (addr,port) is already bound.
    74  			return false
    75  		}
    76  		for _, p := range d {
    77  			if !p.reuse {
    78  				// Can't bind because the (addr,port) was previously bound without reuse.
    79  				return false
    80  			}
    81  		}
    82  		return true
    83  	}
    84  
    85  	if p, ok := d[0]; ok {
    86  		if !reuse || !p.reuse {
    87  			return false
    88  		}
    89  	}
    90  
    91  	if p, ok := d[bindToDevice]; ok {
    92  		if !reuse || !p.reuse {
    93  			return false
    94  		}
    95  	}
    96  
    97  	return true
    98  }
    99  
   100  // bindAddresses is a set of IP addresses.
   101  type bindAddresses map[tcpip.Address]deviceNode
   102  
   103  // isAvailable checks whether an IP address is available to bind to. If the
   104  // address is the "any" address, check all other addresses. Otherwise, just
   105  // check against the "any" address and the provided address.
   106  func (b bindAddresses) isAvailable(addr tcpip.Address, reuse bool, bindToDevice tcpip.NICID) bool {
   107  	if addr == anyIPAddress {
   108  		// If binding to the "any" address then check that there are no conflicts
   109  		// with all addresses.
   110  		for _, d := range b {
   111  			if !d.isAvailable(reuse, bindToDevice) {
   112  				return false
   113  			}
   114  		}
   115  		return true
   116  	}
   117  
   118  	// Check that there is no conflict with the "any" address.
   119  	if d, ok := b[anyIPAddress]; ok {
   120  		if !d.isAvailable(reuse, bindToDevice) {
   121  			return false
   122  		}
   123  	}
   124  
   125  	// Check that this is no conflict with the provided address.
   126  	if d, ok := b[addr]; ok {
   127  		if !d.isAvailable(reuse, bindToDevice) {
   128  			return false
   129  		}
   130  	}
   131  
   132  	return true
   133  }
   134  
   135  // NewPortManager creates new PortManager.
   136  func NewPortManager() *PortManager {
   137  	return &PortManager{allocatedPorts: make(map[portDescriptor]bindAddresses)}
   138  }
   139  
   140  // PickEphemeralPort randomly chooses a starting point and iterates over all
   141  // possible ephemeral ports, allowing the caller to decide whether a given port
   142  // is suitable for its needs, and stopping when a port is found or an error
   143  // occurs.
   144  func (s *PortManager) PickEphemeralPort(testPort func(p uint16) (bool, *tcpip.Error)) (port uint16, err *tcpip.Error) {
   145  	offset := uint32(rand.Int31n(numEphemeralPorts))
   146  	return s.pickEphemeralPort(offset, numEphemeralPorts, testPort)
   147  }
   148  
   149  // portHint atomically reads and returns the s.hint value.
   150  func (s *PortManager) portHint() uint32 {
   151  	return atomic.LoadUint32(&s.hint)
   152  }
   153  
   154  // incPortHint atomically increments s.hint by 1.
   155  func (s *PortManager) incPortHint() {
   156  	atomic.AddUint32(&s.hint, 1)
   157  }
   158  
   159  // PickEphemeralPortStable starts at the specified offset + s.portHint and
   160  // iterates over all ephemeral ports, allowing the caller to decide whether a
   161  // given port is suitable for its needs and stopping when a port is found or an
   162  // error occurs.
   163  func (s *PortManager) PickEphemeralPortStable(offset uint32, testPort func(p uint16) (bool, *tcpip.Error)) (port uint16, err *tcpip.Error) {
   164  	p, err := s.pickEphemeralPort(s.portHint()+offset, numEphemeralPorts, testPort)
   165  	if err == nil {
   166  		s.incPortHint()
   167  	}
   168  	return p, err
   169  
   170  }
   171  
   172  // pickEphemeralPort starts at the offset specified from the FirstEphemeral port
   173  // and iterates over the number of ports specified by count and allows the
   174  // caller to decide whether a given port is suitable for its needs, and stopping
   175  // when a port is found or an error occurs.
   176  func (s *PortManager) pickEphemeralPort(offset, count uint32, testPort func(p uint16) (bool, *tcpip.Error)) (port uint16, err *tcpip.Error) {
   177  	for i := uint32(0); i < count; i++ {
   178  		port = uint16(FirstEphemeral + (offset+i)%count)
   179  		ok, err := testPort(port)
   180  		if err != nil {
   181  			return 0, err
   182  		}
   183  
   184  		if ok {
   185  			return port, nil
   186  		}
   187  	}
   188  
   189  	return 0, tcpip.ErrNoPortAvailable
   190  }
   191  
   192  // IsPortAvailable tests if the given port is available on all given protocols.
   193  func (s *PortManager) IsPortAvailable(networks []tcpip.NetworkProtocolNumber, transport tcpip.TransportProtocolNumber, addr tcpip.Address, port uint16, reuse bool, bindToDevice tcpip.NICID) bool {
   194  	s.mu.Lock()
   195  	defer s.mu.Unlock()
   196  	return s.isPortAvailableLocked(networks, transport, addr, port, reuse, bindToDevice)
   197  }
   198  
   199  func (s *PortManager) isPortAvailableLocked(networks []tcpip.NetworkProtocolNumber, transport tcpip.TransportProtocolNumber, addr tcpip.Address, port uint16, reuse bool, bindToDevice tcpip.NICID) bool {
   200  	for _, network := range networks {
   201  		desc := portDescriptor{network, transport, port}
   202  		if addrs, ok := s.allocatedPorts[desc]; ok {
   203  			if !addrs.isAvailable(addr, reuse, bindToDevice) {
   204  				return false
   205  			}
   206  		}
   207  	}
   208  	return true
   209  }
   210  
   211  // ReservePort marks a port/IP combination as reserved so that it cannot be
   212  // reserved by another endpoint. If port is zero, ReservePort will search for
   213  // an unreserved ephemeral port and reserve it, returning its value in the
   214  // "port" return value.
   215  func (s *PortManager) ReservePort(networks []tcpip.NetworkProtocolNumber, transport tcpip.TransportProtocolNumber, addr tcpip.Address, port uint16, reuse bool, bindToDevice tcpip.NICID) (reservedPort uint16, err *tcpip.Error) {
   216  	s.mu.Lock()
   217  	defer s.mu.Unlock()
   218  
   219  	// If a port is specified, just try to reserve it for all network
   220  	// protocols.
   221  	if port != 0 {
   222  		if !s.reserveSpecificPort(networks, transport, addr, port, reuse, bindToDevice) {
   223  			return 0, tcpip.ErrPortInUse
   224  		}
   225  		return port, nil
   226  	}
   227  
   228  	// A port wasn't specified, so try to find one.
   229  	return s.PickEphemeralPort(func(p uint16) (bool, *tcpip.Error) {
   230  		return s.reserveSpecificPort(networks, transport, addr, p, reuse, bindToDevice), nil
   231  	})
   232  }
   233  
   234  // reserveSpecificPort tries to reserve the given port on all given protocols.
   235  func (s *PortManager) reserveSpecificPort(networks []tcpip.NetworkProtocolNumber, transport tcpip.TransportProtocolNumber, addr tcpip.Address, port uint16, reuse bool, bindToDevice tcpip.NICID) bool {
   236  	if !s.isPortAvailableLocked(networks, transport, addr, port, reuse, bindToDevice) {
   237  		return false
   238  	}
   239  
   240  	// Reserve port on all network protocols.
   241  	for _, network := range networks {
   242  		desc := portDescriptor{network, transport, port}
   243  		m, ok := s.allocatedPorts[desc]
   244  		if !ok {
   245  			m = make(bindAddresses)
   246  			s.allocatedPorts[desc] = m
   247  		}
   248  		d, ok := m[addr]
   249  		if !ok {
   250  			d = make(deviceNode)
   251  			m[addr] = d
   252  		}
   253  		if n, ok := d[bindToDevice]; ok {
   254  			n.refs++
   255  			d[bindToDevice] = n
   256  		} else {
   257  			d[bindToDevice] = portNode{reuse: reuse, refs: 1}
   258  		}
   259  	}
   260  
   261  	return true
   262  }
   263  
   264  // ReleasePort releases the reservation on a port/IP combination so that it can
   265  // be reserved by other endpoints.
   266  func (s *PortManager) ReleasePort(networks []tcpip.NetworkProtocolNumber, transport tcpip.TransportProtocolNumber, addr tcpip.Address, port uint16, bindToDevice tcpip.NICID) {
   267  	s.mu.Lock()
   268  	defer s.mu.Unlock()
   269  
   270  	for _, network := range networks {
   271  		desc := portDescriptor{network, transport, port}
   272  		if m, ok := s.allocatedPorts[desc]; ok {
   273  			d, ok := m[addr]
   274  			if !ok {
   275  				continue
   276  			}
   277  			n, ok := d[bindToDevice]
   278  			if !ok {
   279  				continue
   280  			}
   281  			n.refs--
   282  			d[bindToDevice] = n
   283  			if n.refs == 0 {
   284  				delete(d, bindToDevice)
   285  			}
   286  			if len(d) == 0 {
   287  				delete(m, addr)
   288  			}
   289  			if len(m) == 0 {
   290  				delete(s.allocatedPorts, desc)
   291  			}
   292  		}
   293  	}
   294  }