gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/tcpip/ports/ports_test.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
    16  
    17  import (
    18  	"math"
    19  	"testing"
    20  
    21  	"github.com/google/go-cmp/cmp"
    22  	cryptorand "gvisor.dev/gvisor/pkg/rand"
    23  	"gvisor.dev/gvisor/pkg/tcpip"
    24  	"gvisor.dev/gvisor/pkg/tcpip/testutil"
    25  )
    26  
    27  const (
    28  	fakeTransNumber   tcpip.TransportProtocolNumber = 1
    29  	fakeNetworkNumber tcpip.NetworkProtocolNumber   = 2
    30  )
    31  
    32  var (
    33  	fakeIPAddress  = testutil.MustParse4("8.8.8.8")
    34  	fakeIPAddress1 = testutil.MustParse4("8.8.8.9")
    35  )
    36  
    37  type portReserveTestAction struct {
    38  	port    uint16
    39  	ip      tcpip.Address
    40  	want    tcpip.Error
    41  	flags   Flags
    42  	release bool
    43  	device  tcpip.NICID
    44  	dest    tcpip.FullAddress
    45  }
    46  
    47  func TestPortReservation(t *testing.T) {
    48  	for _, test := range []struct {
    49  		tname   string
    50  		actions []portReserveTestAction
    51  	}{
    52  		{
    53  			tname: "bind to ip",
    54  			actions: []portReserveTestAction{
    55  				{port: 80, ip: fakeIPAddress, want: nil},
    56  				{port: 80, ip: fakeIPAddress1, want: nil},
    57  				/* N.B. Order of tests matters! */
    58  				{port: 80, ip: anyIPAddress, want: &tcpip.ErrPortInUse{}},
    59  				{port: 80, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}, flags: Flags{LoadBalanced: true}},
    60  			},
    61  		},
    62  		{
    63  			tname: "bind to inaddr any",
    64  			actions: []portReserveTestAction{
    65  				{port: 22, ip: anyIPAddress, want: nil},
    66  				{port: 22, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
    67  				/* release fakeIPAddress, but anyIPAddress is still inuse */
    68  				{port: 22, ip: fakeIPAddress, release: true},
    69  				{port: 22, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
    70  				{port: 22, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}, flags: Flags{LoadBalanced: true}},
    71  				/* Release port 22 from any IP address, then try to reserve fake IP address on 22 */
    72  				{port: 22, ip: anyIPAddress, want: nil, release: true},
    73  				{port: 22, ip: fakeIPAddress, want: nil},
    74  			},
    75  		}, {
    76  			tname: "bind to zero port",
    77  			actions: []portReserveTestAction{
    78  				{port: 00, ip: fakeIPAddress, want: nil},
    79  				{port: 00, ip: fakeIPAddress, want: nil},
    80  				{port: 00, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
    81  			},
    82  		}, {
    83  			tname: "bind to ip with reuseport",
    84  			actions: []portReserveTestAction{
    85  				{port: 25, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
    86  				{port: 25, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
    87  
    88  				{port: 25, ip: fakeIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
    89  				{port: 25, ip: anyIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
    90  
    91  				{port: 25, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
    92  			},
    93  		}, {
    94  			tname: "bind to inaddr any with reuseport",
    95  			actions: []portReserveTestAction{
    96  				{port: 24, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
    97  				{port: 24, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
    98  
    99  				{port: 24, ip: anyIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
   100  				{port: 24, ip: fakeIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
   101  
   102  				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
   103  				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, release: true, want: nil},
   104  
   105  				{port: 24, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, release: true},
   106  				{port: 24, ip: anyIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
   107  
   108  				{port: 24, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, release: true},
   109  				{port: 24, ip: anyIPAddress, flags: Flags{}, want: nil},
   110  			},
   111  		}, {
   112  			tname: "bind twice with device fails",
   113  			actions: []portReserveTestAction{
   114  				{port: 24, ip: fakeIPAddress, device: 3, want: nil},
   115  				{port: 24, ip: fakeIPAddress, device: 3, want: &tcpip.ErrPortInUse{}},
   116  			},
   117  		}, {
   118  			tname: "bind to device",
   119  			actions: []portReserveTestAction{
   120  				{port: 24, ip: fakeIPAddress, device: 1, want: nil},
   121  				{port: 24, ip: fakeIPAddress, device: 2, want: nil},
   122  			},
   123  		}, {
   124  			tname: "bind to device and then without device",
   125  			actions: []portReserveTestAction{
   126  				{port: 24, ip: fakeIPAddress, device: 123, want: nil},
   127  				{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
   128  			},
   129  		}, {
   130  			tname: "bind without device",
   131  			actions: []portReserveTestAction{
   132  				{port: 24, ip: fakeIPAddress, want: nil},
   133  				{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
   134  				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
   135  				{port: 24, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
   136  				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
   137  			},
   138  		}, {
   139  			tname: "bind with device",
   140  			actions: []portReserveTestAction{
   141  				{port: 24, ip: fakeIPAddress, device: 123, want: nil},
   142  				{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
   143  				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
   144  				{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
   145  				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
   146  				{port: 24, ip: fakeIPAddress, device: 456, flags: Flags{LoadBalanced: true}, want: nil},
   147  				{port: 24, ip: fakeIPAddress, device: 789, want: nil},
   148  				{port: 24, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
   149  				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
   150  			},
   151  		}, {
   152  			tname: "bind with reuseport",
   153  			actions: []portReserveTestAction{
   154  				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
   155  				{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
   156  				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
   157  				{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
   158  				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: nil},
   159  			},
   160  		}, {
   161  			tname: "binding with reuseport and device",
   162  			actions: []portReserveTestAction{
   163  				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
   164  				{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
   165  				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
   166  				{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
   167  				{port: 24, ip: fakeIPAddress, device: 456, flags: Flags{LoadBalanced: true}, want: nil},
   168  				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: nil},
   169  				{port: 24, ip: fakeIPAddress, device: 789, flags: Flags{LoadBalanced: true}, want: nil},
   170  				{port: 24, ip: fakeIPAddress, device: 999, want: &tcpip.ErrPortInUse{}},
   171  			},
   172  		}, {
   173  			tname: "mixing reuseport and not reuseport by binding to device",
   174  			actions: []portReserveTestAction{
   175  				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
   176  				{port: 24, ip: fakeIPAddress, device: 456, want: nil},
   177  				{port: 24, ip: fakeIPAddress, device: 789, flags: Flags{LoadBalanced: true}, want: nil},
   178  				{port: 24, ip: fakeIPAddress, device: 999, want: nil},
   179  			},
   180  		}, {
   181  			tname: "can't bind to 0 after mixing reuseport and not reuseport",
   182  			actions: []portReserveTestAction{
   183  				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
   184  				{port: 24, ip: fakeIPAddress, device: 456, want: nil},
   185  				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
   186  			},
   187  		}, {
   188  			tname: "bind and release",
   189  			actions: []portReserveTestAction{
   190  				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
   191  				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: nil},
   192  				{port: 24, ip: fakeIPAddress, device: 345, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
   193  				{port: 24, ip: fakeIPAddress, device: 789, flags: Flags{LoadBalanced: true}, want: nil},
   194  
   195  				// Release the bind to device 0 and try again.
   196  				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: nil, release: true},
   197  				{port: 24, ip: fakeIPAddress, device: 345, flags: Flags{}, want: nil},
   198  			},
   199  		}, {
   200  			tname: "bind twice with reuseport once",
   201  			actions: []portReserveTestAction{
   202  				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil},
   203  				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
   204  			},
   205  		}, {
   206  			tname: "release an unreserved device",
   207  			actions: []portReserveTestAction{
   208  				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil},
   209  				{port: 24, ip: fakeIPAddress, device: 456, flags: Flags{}, want: nil},
   210  				// The below don't exist.
   211  				{port: 24, ip: fakeIPAddress, device: 345, flags: Flags{}, want: nil, release: true},
   212  				{port: 9999, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil, release: true},
   213  				// Release all.
   214  				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil, release: true},
   215  				{port: 24, ip: fakeIPAddress, device: 456, flags: Flags{}, want: nil, release: true},
   216  			},
   217  		}, {
   218  			tname: "bind with reuseaddr",
   219  			actions: []portReserveTestAction{
   220  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: nil},
   221  				{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
   222  				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{MostRecent: true}, want: nil},
   223  				{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
   224  				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{MostRecent: true}, want: nil},
   225  			},
   226  		}, {
   227  			tname: "bind twice with reuseaddr once",
   228  			actions: []portReserveTestAction{
   229  				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil},
   230  				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{MostRecent: true}, want: &tcpip.ErrPortInUse{}},
   231  			},
   232  		}, {
   233  			tname: "bind with reuseaddr and reuseport",
   234  			actions: []portReserveTestAction{
   235  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
   236  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
   237  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
   238  			},
   239  		}, {
   240  			tname: "bind with reuseaddr and reuseport, and then reuseaddr",
   241  			actions: []portReserveTestAction{
   242  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
   243  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: nil},
   244  				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
   245  			},
   246  		}, {
   247  			tname: "bind with reuseaddr and reuseport, and then reuseport",
   248  			actions: []portReserveTestAction{
   249  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
   250  				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
   251  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: &tcpip.ErrPortInUse{}},
   252  			},
   253  		}, {
   254  			tname: "bind with reuseaddr and reuseport twice, and then reuseaddr",
   255  			actions: []portReserveTestAction{
   256  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
   257  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
   258  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: nil},
   259  			},
   260  		}, {
   261  			tname: "bind with reuseaddr and reuseport twice, and then reuseport",
   262  			actions: []portReserveTestAction{
   263  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
   264  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
   265  				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
   266  			},
   267  		}, {
   268  			tname: "bind with reuseaddr, and then reuseaddr and reuseport",
   269  			actions: []portReserveTestAction{
   270  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: nil},
   271  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
   272  				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
   273  			},
   274  		}, {
   275  			tname: "bind with reuseport, and then reuseaddr and reuseport",
   276  			actions: []portReserveTestAction{
   277  				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
   278  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
   279  				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: &tcpip.ErrPortInUse{}},
   280  			},
   281  		}, {
   282  			tname: "bind tuple with reuseaddr, and then wildcard with reuseaddr",
   283  			actions: []portReserveTestAction{
   284  				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
   285  				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{}, want: nil},
   286  			},
   287  		}, {
   288  			tname: "bind tuple with reuseaddr, and then wildcard",
   289  			actions: []portReserveTestAction{
   290  				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
   291  				{port: 24, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
   292  			},
   293  		}, {
   294  			tname: "bind wildcard with reuseaddr, and then tuple with reuseaddr",
   295  			actions: []portReserveTestAction{
   296  				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{}, want: nil},
   297  				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
   298  			},
   299  		}, {
   300  			tname: "bind tuple with reuseaddr, and then wildcard",
   301  			actions: []portReserveTestAction{
   302  				{port: 24, ip: fakeIPAddress, want: nil},
   303  				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: &tcpip.ErrPortInUse{}},
   304  			},
   305  		}, {
   306  			tname: "bind two tuples with reuseaddr",
   307  			actions: []portReserveTestAction{
   308  				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
   309  				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 25}, want: nil},
   310  			},
   311  		}, {
   312  			tname: "bind two tuples",
   313  			actions: []portReserveTestAction{
   314  				{port: 24, ip: fakeIPAddress, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
   315  				{port: 24, ip: fakeIPAddress, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 25}, want: nil},
   316  			},
   317  		}, {
   318  			tname: "bind wildcard, and then tuple with reuseaddr",
   319  			actions: []portReserveTestAction{
   320  				{port: 24, ip: fakeIPAddress, dest: tcpip.FullAddress{}, want: nil},
   321  				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: &tcpip.ErrPortInUse{}},
   322  			},
   323  		}, {
   324  			tname: "bind wildcard twice with reuseaddr",
   325  			actions: []portReserveTestAction{
   326  				{port: 24, ip: anyIPAddress, flags: Flags{TupleOnly: true}, want: nil},
   327  				{port: 24, ip: anyIPAddress, flags: Flags{TupleOnly: true}, want: nil},
   328  			},
   329  		},
   330  	} {
   331  		t.Run(test.tname, func(t *testing.T) {
   332  			pm := NewPortManager()
   333  			net := []tcpip.NetworkProtocolNumber{fakeNetworkNumber}
   334  			rng := cryptorand.RNGFrom(cryptorand.Reader)
   335  
   336  			for _, test := range test.actions {
   337  				first, _ := pm.PortRange()
   338  				if test.release {
   339  					portRes := Reservation{
   340  						Networks:     net,
   341  						Transport:    fakeTransNumber,
   342  						Addr:         test.ip,
   343  						Port:         test.port,
   344  						Flags:        test.flags,
   345  						BindToDevice: test.device,
   346  						Dest:         test.dest,
   347  					}
   348  					pm.ReleasePort(portRes)
   349  					continue
   350  				}
   351  				portRes := Reservation{
   352  					Networks:     net,
   353  					Transport:    fakeTransNumber,
   354  					Addr:         test.ip,
   355  					Port:         test.port,
   356  					Flags:        test.flags,
   357  					BindToDevice: test.device,
   358  					Dest:         test.dest,
   359  				}
   360  				gotPort, err := pm.ReservePort(rng, portRes, nil /* testPort */)
   361  				if diff := cmp.Diff(test.want, err); diff != "" {
   362  					t.Fatalf("unexpected error from ReservePort(%+v, _), (-want, +got):\n%s", portRes, diff)
   363  				}
   364  				if test.port == 0 && (gotPort == 0 || gotPort < first) {
   365  					t.Fatalf("ReservePort(%+v, _) = %d, want port number >= %d to be picked", portRes, gotPort, first)
   366  				}
   367  			}
   368  		})
   369  	}
   370  }
   371  
   372  func TestPickEphemeralPort(t *testing.T) {
   373  	const (
   374  		firstEphemeral    = 32000
   375  		numEphemeralPorts = 1000
   376  	)
   377  
   378  	for _, test := range []struct {
   379  		name     string
   380  		f        func(port uint16) (bool, tcpip.Error)
   381  		wantErr  tcpip.Error
   382  		wantPort uint16
   383  	}{
   384  		{
   385  			name: "no-port-available",
   386  			f: func(port uint16) (bool, tcpip.Error) {
   387  				return false, nil
   388  			},
   389  			wantErr: &tcpip.ErrNoPortAvailable{},
   390  		},
   391  		{
   392  			name: "port-tester-error",
   393  			f: func(port uint16) (bool, tcpip.Error) {
   394  				return false, &tcpip.ErrBadBuffer{}
   395  			},
   396  			wantErr: &tcpip.ErrBadBuffer{},
   397  		},
   398  		{
   399  			name: "only-port-16042-available",
   400  			f: func(port uint16) (bool, tcpip.Error) {
   401  				if port == firstEphemeral+42 {
   402  					return true, nil
   403  				}
   404  				return false, nil
   405  			},
   406  			wantPort: firstEphemeral + 42,
   407  		},
   408  		{
   409  			name: "only-port-under-16000-available",
   410  			f: func(port uint16) (bool, tcpip.Error) {
   411  				if port < firstEphemeral {
   412  					return true, nil
   413  				}
   414  				return false, nil
   415  			},
   416  			wantErr: &tcpip.ErrNoPortAvailable{},
   417  		},
   418  	} {
   419  		t.Run(test.name, func(t *testing.T) {
   420  			pm := NewPortManager()
   421  			rng := cryptorand.RNGFrom(cryptorand.Reader)
   422  			if err := pm.SetPortRange(firstEphemeral, firstEphemeral+numEphemeralPorts); err != nil {
   423  				t.Fatalf("failed to set ephemeral port range: %s", err)
   424  			}
   425  			port, err := pm.PickEphemeralPort(rng, test.f)
   426  			if diff := cmp.Diff(test.wantErr, err); diff != "" {
   427  				t.Fatalf("unexpected error from PickEphemeralPort(..), (-want, +got):\n%s", diff)
   428  			}
   429  			if port != test.wantPort {
   430  				t.Errorf("got PickEphemeralPort(..) = (%d, nil); want (%d, nil)", port, test.wantPort)
   431  			}
   432  		})
   433  	}
   434  }
   435  
   436  // TestOverflow addresses b/183593432, wherein an overflowing uint16 causes a
   437  // port allocation failure.
   438  func TestOverflow(t *testing.T) {
   439  	// Use a small range and start at offsets that will cause an overflow.
   440  	count := uint16(50)
   441  	for offset := uint32(math.MaxUint16 - count); offset < math.MaxUint16; offset++ {
   442  		reservedPorts := make(map[uint16]struct{})
   443  		// Ensure we can reserve everything in the allowed range.
   444  		for i := uint16(0); i < count; i++ {
   445  			port, err := pickEphemeralPort(offset, firstEphemeral, count, func(port uint16) (bool, tcpip.Error) {
   446  				if _, ok := reservedPorts[port]; !ok {
   447  					reservedPorts[port] = struct{}{}
   448  					return true, nil
   449  				}
   450  				return false, nil
   451  			})
   452  			if err != nil {
   453  				t.Fatalf("port picking failed at iteration %d, for offset %d, len(reserved): %+v", i, offset, len(reservedPorts))
   454  			}
   455  			if port < firstEphemeral || port > firstEphemeral+count {
   456  				t.Fatalf("reserved port %d, which is not in range [%d, %d]", port, firstEphemeral, firstEphemeral+count-1)
   457  			}
   458  		}
   459  	}
   460  }