github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/pkg/sentry/socket/netlink/port/port.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 port provides port ID allocation for netlink sockets.
    16  //
    17  // A netlink port is any int32 value. Positive ports are typically equivalent
    18  // to the PID of the binding process. If that port is unavailable, negative
    19  // ports are searched to find a free port that will not conflict with other
    20  // PIDS.
    21  package port
    22  
    23  import (
    24  	"fmt"
    25  	"math"
    26  	"math/rand"
    27  
    28  	"github.com/MerlinKodo/gvisor/pkg/sync"
    29  )
    30  
    31  // maxPorts is a sanity limit on the maximum number of ports to allocate per
    32  // protocol.
    33  const maxPorts = 10000
    34  
    35  // Manager allocates netlink port IDs.
    36  //
    37  // +stateify savable
    38  type Manager struct {
    39  	// mu protects the fields below.
    40  	mu sync.Mutex `state:"nosave"`
    41  
    42  	// ports contains a map of allocated ports for each protocol.
    43  	ports map[int]map[int32]struct{}
    44  }
    45  
    46  // New creates a new Manager.
    47  func New() *Manager {
    48  	return &Manager{
    49  		ports: make(map[int]map[int32]struct{}),
    50  	}
    51  }
    52  
    53  // Allocate reserves a new port ID for protocol. hint will be taken if
    54  // available.
    55  func (m *Manager) Allocate(protocol int, hint int32) (int32, bool) {
    56  	m.mu.Lock()
    57  	defer m.mu.Unlock()
    58  
    59  	proto, ok := m.ports[protocol]
    60  	if !ok {
    61  		proto = make(map[int32]struct{})
    62  		// Port 0 is reserved for the kernel.
    63  		proto[0] = struct{}{}
    64  		m.ports[protocol] = proto
    65  	}
    66  
    67  	if len(proto) >= maxPorts {
    68  		return 0, false
    69  	}
    70  
    71  	if _, ok := proto[hint]; !ok {
    72  		// Hint is available, reserve it.
    73  		proto[hint] = struct{}{}
    74  		return hint, true
    75  	}
    76  
    77  	// Search for any free port in [math.MinInt32, -4096). The positive
    78  	// port space is left open for pid-based allocations. This behavior is
    79  	// consistent with Linux.
    80  	start := int32(math.MinInt32 + rand.Int63n(math.MaxInt32-4096+1))
    81  	curr := start
    82  	for {
    83  		if _, ok := proto[curr]; !ok {
    84  			proto[curr] = struct{}{}
    85  			return curr, true
    86  		}
    87  
    88  		curr--
    89  		if curr >= -4096 {
    90  			curr = -4097
    91  		}
    92  		if curr == start {
    93  			// Nothing found. We should always find a free port
    94  			// because maxPorts < -4096 - MinInt32.
    95  			panic(fmt.Sprintf("No free port found in %+v", proto))
    96  		}
    97  	}
    98  }
    99  
   100  // Release frees the specified port for protocol.
   101  //
   102  // Preconditions: port is already allocated.
   103  func (m *Manager) Release(protocol int, port int32) {
   104  	m.mu.Lock()
   105  	defer m.mu.Unlock()
   106  
   107  	proto, ok := m.ports[protocol]
   108  	if !ok {
   109  		panic(fmt.Sprintf("Released port %d for protocol %d which has no allocations", port, protocol))
   110  	}
   111  
   112  	if _, ok := proto[port]; !ok {
   113  		panic(fmt.Sprintf("Released port %d for protocol %d is not allocated", port, protocol))
   114  	}
   115  
   116  	delete(proto, port)
   117  }