summaryrefslogtreecommitdiffhomepage
path: root/pkg/tcpip/ports
diff options
context:
space:
mode:
authorKevin Krakauer <krakauer@google.com>2021-03-08 20:37:14 -0800
committergVisor bot <gvisor-bot@google.com>2021-03-08 20:40:34 -0800
commitabbdcebc543242862fad0984db2db0a842021917 (patch)
tree28e4dc26f8e7f862cc7e5a4112e1ca0b6d90c3dd /pkg/tcpip/ports
parent3c4485966c170850bb677efc88de4c0ecaac1358 (diff)
Implement /proc/sys/net/ipv4/ip_local_port_range
Speeds up the socket stress tests by a couple orders of magnitude. PiperOrigin-RevId: 361721050
Diffstat (limited to 'pkg/tcpip/ports')
-rw-r--r--pkg/tcpip/ports/ports.go76
-rw-r--r--pkg/tcpip/ports/ports_test.go35
2 files changed, 81 insertions, 30 deletions
diff --git a/pkg/tcpip/ports/ports.go b/pkg/tcpip/ports/ports.go
index 11dbdbbcf..101872b47 100644
--- a/pkg/tcpip/ports/ports.go
+++ b/pkg/tcpip/ports/ports.go
@@ -16,7 +16,6 @@
package ports
import (
- "math"
"math/rand"
"sync/atomic"
@@ -24,16 +23,7 @@ import (
"gvisor.dev/gvisor/pkg/tcpip"
)
-const (
- // FirstEphemeral is the first ephemeral port.
- FirstEphemeral = 16000
-
- // numEphemeralPorts it the mnumber of available ephemeral ports to
- // Netstack.
- numEphemeralPorts = math.MaxUint16 - FirstEphemeral + 1
-
- anyIPAddress tcpip.Address = ""
-)
+const anyIPAddress tcpip.Address = ""
type portDescriptor struct {
network tcpip.NetworkProtocolNumber
@@ -83,9 +73,16 @@ func (f Flags) Effective() Flags {
// PortManager manages allocating, reserving and releasing ports.
type PortManager struct {
+ // mu protects allocatedPorts.
+ // LOCK ORDERING: mu > ephemeralMu.
mu sync.RWMutex
allocatedPorts map[portDescriptor]bindAddresses
+ // ephemeralMu protects firstEphemeral and numEphemeral.
+ ephemeralMu sync.RWMutex
+ firstEphemeral uint16
+ numEphemeral uint16
+
// hint is used to pick ports ephemeral ports in a stable order for
// a given port offset.
//
@@ -322,7 +319,13 @@ func (b bindAddresses) isAvailable(addr tcpip.Address, flags Flags, bindToDevice
// NewPortManager creates new PortManager.
func NewPortManager() *PortManager {
- return &PortManager{allocatedPorts: make(map[portDescriptor]bindAddresses)}
+ return &PortManager{
+ allocatedPorts: make(map[portDescriptor]bindAddresses),
+ // Match Linux's default ephemeral range. See:
+ // https://github.com/torvalds/linux/blob/e54937963fa249595824439dc839c948188dea83/net/ipv4/af_inet.c#L1842
+ firstEphemeral: 32768,
+ numEphemeral: 28232,
+ }
}
// PickEphemeralPort randomly chooses a starting point and iterates over all
@@ -330,13 +333,18 @@ func NewPortManager() *PortManager {
// is suitable for its needs, and stopping when a port is found or an error
// occurs.
func (s *PortManager) PickEphemeralPort(testPort func(p uint16) (bool, tcpip.Error)) (port uint16, err tcpip.Error) {
- offset := uint32(rand.Int31n(numEphemeralPorts))
- return s.pickEphemeralPort(offset, numEphemeralPorts, testPort)
+ s.ephemeralMu.RLock()
+ firstEphemeral := s.firstEphemeral
+ numEphemeral := s.numEphemeral
+ s.ephemeralMu.RUnlock()
+
+ offset := uint16(rand.Int31n(int32(numEphemeral)))
+ return pickEphemeralPort(offset, firstEphemeral, numEphemeral, testPort)
}
// portHint atomically reads and returns the s.hint value.
-func (s *PortManager) portHint() uint32 {
- return atomic.LoadUint32(&s.hint)
+func (s *PortManager) portHint() uint16 {
+ return uint16(atomic.LoadUint32(&s.hint))
}
// incPortHint atomically increments s.hint by 1.
@@ -348,8 +356,13 @@ func (s *PortManager) incPortHint() {
// iterates over all ephemeral ports, allowing the caller to decide whether a
// given port is suitable for its needs and stopping when a port is found or an
// error occurs.
-func (s *PortManager) PickEphemeralPortStable(offset uint32, testPort func(p uint16) (bool, tcpip.Error)) (port uint16, err tcpip.Error) {
- p, err := s.pickEphemeralPort(s.portHint()+offset, numEphemeralPorts, testPort)
+func (s *PortManager) PickEphemeralPortStable(offset uint16, testPort func(p uint16) (bool, tcpip.Error)) (port uint16, err tcpip.Error) {
+ s.ephemeralMu.RLock()
+ firstEphemeral := s.firstEphemeral
+ numEphemeral := s.numEphemeral
+ s.ephemeralMu.RUnlock()
+
+ p, err := pickEphemeralPort(s.portHint()+offset, firstEphemeral, numEphemeral, testPort)
if err == nil {
s.incPortHint()
}
@@ -361,9 +374,9 @@ func (s *PortManager) PickEphemeralPortStable(offset uint32, testPort func(p uin
// and iterates over the number of ports specified by count and allows the
// caller to decide whether a given port is suitable for its needs, and stopping
// when a port is found or an error occurs.
-func (s *PortManager) pickEphemeralPort(offset, count uint32, testPort func(p uint16) (bool, tcpip.Error)) (port uint16, err tcpip.Error) {
- for i := uint32(0); i < count; i++ {
- port = uint16(FirstEphemeral + (offset+i)%count)
+func pickEphemeralPort(offset, first, count uint16, testPort func(p uint16) (bool, tcpip.Error)) (port uint16, err tcpip.Error) {
+ for i := uint16(0); i < count; i++ {
+ port = first + (offset+i)%count
ok, err := testPort(port)
if err != nil {
return 0, err
@@ -567,3 +580,24 @@ func (s *PortManager) releasePortLocked(networks []tcpip.NetworkProtocolNumber,
}
}
}
+
+// PortRange returns the UDP and TCP inclusive range of ephemeral ports used in
+// both IPv4 and IPv6.
+func (s *PortManager) PortRange() (uint16, uint16) {
+ s.ephemeralMu.RLock()
+ defer s.ephemeralMu.RUnlock()
+ return s.firstEphemeral, s.firstEphemeral + s.numEphemeral - 1
+}
+
+// SetPortRange sets the UDP and TCP IPv4 and IPv6 ephemeral port range
+// (inclusive).
+func (s *PortManager) SetPortRange(start uint16, end uint16) tcpip.Error {
+ if start > end {
+ return &tcpip.ErrInvalidPortRange{}
+ }
+ s.ephemeralMu.Lock()
+ defer s.ephemeralMu.Unlock()
+ s.firstEphemeral = start
+ s.numEphemeral = end - start + 1
+ return nil
+}
diff --git a/pkg/tcpip/ports/ports_test.go b/pkg/tcpip/ports/ports_test.go
index e70fbb72b..6cfac04b1 100644
--- a/pkg/tcpip/ports/ports_test.go
+++ b/pkg/tcpip/ports/ports_test.go
@@ -329,6 +329,7 @@ func TestPortReservation(t *testing.T) {
net := []tcpip.NetworkProtocolNumber{fakeNetworkNumber}
for _, test := range test.actions {
+ first, _ := pm.PortRange()
if test.release {
pm.ReleasePort(net, fakeTransNumber, test.ip, test.port, test.flags, test.device, test.dest)
continue
@@ -337,8 +338,8 @@ func TestPortReservation(t *testing.T) {
if diff := cmp.Diff(test.want, err); diff != "" {
t.Fatalf("unexpected error from ReservePort(.., .., %s, %d, %+v, %d, %v), (-want, +got):\n%s", test.ip, test.port, test.flags, test.device, test.dest, diff)
}
- if test.port == 0 && (gotPort == 0 || gotPort < FirstEphemeral) {
- t.Fatalf("ReservePort(.., .., .., 0, ..) = %d, want port number >= %d to be picked", gotPort, FirstEphemeral)
+ if test.port == 0 && (gotPort == 0 || gotPort < first) {
+ t.Fatalf("ReservePort(.., .., .., 0, ..) = %d, want port number >= %d to be picked", gotPort, first)
}
}
})
@@ -346,6 +347,11 @@ func TestPortReservation(t *testing.T) {
}
func TestPickEphemeralPort(t *testing.T) {
+ const (
+ firstEphemeral = 32000
+ numEphemeralPorts = 1000
+ )
+
for _, test := range []struct {
name string
f func(port uint16) (bool, tcpip.Error)
@@ -369,17 +375,17 @@ func TestPickEphemeralPort(t *testing.T) {
{
name: "only-port-16042-available",
f: func(port uint16) (bool, tcpip.Error) {
- if port == FirstEphemeral+42 {
+ if port == firstEphemeral+42 {
return true, nil
}
return false, nil
},
- wantPort: FirstEphemeral + 42,
+ wantPort: firstEphemeral + 42,
},
{
name: "only-port-under-16000-available",
f: func(port uint16) (bool, tcpip.Error) {
- if port < FirstEphemeral {
+ if port < firstEphemeral {
return true, nil
}
return false, nil
@@ -389,6 +395,9 @@ func TestPickEphemeralPort(t *testing.T) {
} {
t.Run(test.name, func(t *testing.T) {
pm := NewPortManager()
+ if err := pm.SetPortRange(firstEphemeral, firstEphemeral+numEphemeralPorts); err != nil {
+ t.Fatalf("failed to set ephemeral port range: %s", err)
+ }
port, err := pm.PickEphemeralPort(test.f)
if diff := cmp.Diff(test.wantErr, err); diff != "" {
t.Fatalf("unexpected error from PickEphemeralPort(..), (-want, +got):\n%s", diff)
@@ -401,6 +410,11 @@ func TestPickEphemeralPort(t *testing.T) {
}
func TestPickEphemeralPortStable(t *testing.T) {
+ const (
+ firstEphemeral = 32000
+ numEphemeralPorts = 1000
+ )
+
for _, test := range []struct {
name string
f func(port uint16) (bool, tcpip.Error)
@@ -424,17 +438,17 @@ func TestPickEphemeralPortStable(t *testing.T) {
{
name: "only-port-16042-available",
f: func(port uint16) (bool, tcpip.Error) {
- if port == FirstEphemeral+42 {
+ if port == firstEphemeral+42 {
return true, nil
}
return false, nil
},
- wantPort: FirstEphemeral + 42,
+ wantPort: firstEphemeral + 42,
},
{
name: "only-port-under-16000-available",
f: func(port uint16) (bool, tcpip.Error) {
- if port < FirstEphemeral {
+ if port < firstEphemeral {
return true, nil
}
return false, nil
@@ -444,7 +458,10 @@ func TestPickEphemeralPortStable(t *testing.T) {
} {
t.Run(test.name, func(t *testing.T) {
pm := NewPortManager()
- portOffset := uint32(rand.Int31n(int32(numEphemeralPorts)))
+ if err := pm.SetPortRange(firstEphemeral, firstEphemeral+numEphemeralPorts); err != nil {
+ t.Fatalf("failed to set ephemeral port range: %s", err)
+ }
+ portOffset := uint16(rand.Int31n(int32(numEphemeralPorts)))
port, err := pm.PickEphemeralPortStable(portOffset, test.f)
if diff := cmp.Diff(test.wantErr, err); diff != "" {
t.Fatalf("unexpected error from PickEphemeralPort(..), (-want, +got):\n%s", diff)