526 lines
21 KiB
Go
526 lines
21 KiB
Go
// Copyright 2018 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package ports
|
|
|
|
import (
|
|
"math"
|
|
"math/rand"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/testutil"
|
|
)
|
|
|
|
const (
|
|
fakeTransNumber tcpip.TransportProtocolNumber = 1
|
|
fakeNetworkNumber tcpip.NetworkProtocolNumber = 2
|
|
)
|
|
|
|
var (
|
|
fakeIPAddress = testutil.MustParse4("8.8.8.8")
|
|
fakeIPAddress1 = testutil.MustParse4("8.8.8.9")
|
|
)
|
|
|
|
type portReserveTestAction struct {
|
|
port uint16
|
|
ip tcpip.Address
|
|
want tcpip.Error
|
|
flags Flags
|
|
release bool
|
|
device tcpip.NICID
|
|
dest tcpip.FullAddress
|
|
}
|
|
|
|
func TestPortReservation(t *testing.T) {
|
|
for _, test := range []struct {
|
|
tname string
|
|
actions []portReserveTestAction
|
|
}{
|
|
{
|
|
tname: "bind to ip",
|
|
actions: []portReserveTestAction{
|
|
{port: 80, ip: fakeIPAddress, want: nil},
|
|
{port: 80, ip: fakeIPAddress1, want: nil},
|
|
/* N.B. Order of tests matters! */
|
|
{port: 80, ip: anyIPAddress, want: &tcpip.ErrPortInUse{}},
|
|
{port: 80, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}, flags: Flags{LoadBalanced: true}},
|
|
},
|
|
},
|
|
{
|
|
tname: "bind to inaddr any",
|
|
actions: []portReserveTestAction{
|
|
{port: 22, ip: anyIPAddress, want: nil},
|
|
{port: 22, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
|
|
/* release fakeIPAddress, but anyIPAddress is still inuse */
|
|
{port: 22, ip: fakeIPAddress, release: true},
|
|
{port: 22, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
|
|
{port: 22, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}, flags: Flags{LoadBalanced: true}},
|
|
/* Release port 22 from any IP address, then try to reserve fake IP address on 22 */
|
|
{port: 22, ip: anyIPAddress, want: nil, release: true},
|
|
{port: 22, ip: fakeIPAddress, want: nil},
|
|
},
|
|
}, {
|
|
tname: "bind to zero port",
|
|
actions: []portReserveTestAction{
|
|
{port: 00, ip: fakeIPAddress, want: nil},
|
|
{port: 00, ip: fakeIPAddress, want: nil},
|
|
{port: 00, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
|
|
},
|
|
}, {
|
|
tname: "bind to ip with reuseport",
|
|
actions: []portReserveTestAction{
|
|
{port: 25, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 25, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
|
|
|
|
{port: 25, ip: fakeIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
|
|
{port: 25, ip: anyIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
|
|
|
|
{port: 25, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
|
|
},
|
|
}, {
|
|
tname: "bind to inaddr any with reuseport",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
|
|
|
|
{port: 24, ip: anyIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
|
|
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, release: true, want: nil},
|
|
|
|
{port: 24, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, release: true},
|
|
{port: 24, ip: anyIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
|
|
|
|
{port: 24, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, release: true},
|
|
{port: 24, ip: anyIPAddress, flags: Flags{}, want: nil},
|
|
},
|
|
}, {
|
|
tname: "bind twice with device fails",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, device: 3, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 3, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "bind to device",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, device: 1, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 2, want: nil},
|
|
},
|
|
}, {
|
|
tname: "bind to device and then without device",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, device: 123, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "bind without device",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "bind with device",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, device: 123, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, device: 456, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 789, want: nil},
|
|
{port: 24, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "bind with reuseport",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: nil},
|
|
},
|
|
}, {
|
|
tname: "binding with reuseport and device",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, device: 456, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 789, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 999, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "mixing reuseport and not reuseport by binding to device",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 456, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 789, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 999, want: nil},
|
|
},
|
|
}, {
|
|
tname: "can't bind to 0 after mixing reuseport and not reuseport",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 456, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "bind and release",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 345, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, device: 789, flags: Flags{LoadBalanced: true}, want: nil},
|
|
|
|
// Release the bind to device 0 and try again.
|
|
{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: nil, release: true},
|
|
{port: 24, ip: fakeIPAddress, device: 345, flags: Flags{}, want: nil},
|
|
},
|
|
}, {
|
|
tname: "bind twice with reuseport once",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "release an unreserved device",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 456, flags: Flags{}, want: nil},
|
|
// The below don't exist.
|
|
{port: 24, ip: fakeIPAddress, device: 345, flags: Flags{}, want: nil, release: true},
|
|
{port: 9999, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil, release: true},
|
|
// Release all.
|
|
{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil, release: true},
|
|
{port: 24, ip: fakeIPAddress, device: 456, flags: Flags{}, want: nil, release: true},
|
|
},
|
|
}, {
|
|
tname: "bind with reuseaddr",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{MostRecent: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
|
|
{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{MostRecent: true}, want: nil},
|
|
},
|
|
}, {
|
|
tname: "bind twice with reuseaddr once",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{MostRecent: true}, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "bind with reuseaddr and reuseport",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
|
|
},
|
|
}, {
|
|
tname: "bind with reuseaddr and reuseport, and then reuseaddr",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "bind with reuseaddr and reuseport, and then reuseport",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "bind with reuseaddr and reuseport twice, and then reuseaddr",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: nil},
|
|
},
|
|
}, {
|
|
tname: "bind with reuseaddr and reuseport twice, and then reuseport",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
|
|
},
|
|
}, {
|
|
tname: "bind with reuseaddr, and then reuseaddr and reuseport",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "bind with reuseport, and then reuseaddr and reuseport",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "bind tuple with reuseaddr, and then wildcard with reuseaddr",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{}, want: nil},
|
|
},
|
|
}, {
|
|
tname: "bind tuple with reuseaddr, and then wildcard",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "bind wildcard with reuseaddr, and then tuple with reuseaddr",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
|
|
},
|
|
}, {
|
|
tname: "bind tuple with reuseaddr, and then wildcard",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "bind two tuples with reuseaddr",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 25}, want: nil},
|
|
},
|
|
}, {
|
|
tname: "bind two tuples",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 25}, want: nil},
|
|
},
|
|
}, {
|
|
tname: "bind wildcard, and then tuple with reuseaddr",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: fakeIPAddress, dest: tcpip.FullAddress{}, want: nil},
|
|
{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: &tcpip.ErrPortInUse{}},
|
|
},
|
|
}, {
|
|
tname: "bind wildcard twice with reuseaddr",
|
|
actions: []portReserveTestAction{
|
|
{port: 24, ip: anyIPAddress, flags: Flags{TupleOnly: true}, want: nil},
|
|
{port: 24, ip: anyIPAddress, flags: Flags{TupleOnly: true}, want: nil},
|
|
},
|
|
},
|
|
} {
|
|
t.Run(test.tname, func(t *testing.T) {
|
|
pm := NewPortManager()
|
|
net := []tcpip.NetworkProtocolNumber{fakeNetworkNumber}
|
|
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
for _, test := range test.actions {
|
|
first, _ := pm.PortRange()
|
|
if test.release {
|
|
portRes := Reservation{
|
|
Networks: net,
|
|
Transport: fakeTransNumber,
|
|
Addr: test.ip,
|
|
Port: test.port,
|
|
Flags: test.flags,
|
|
BindToDevice: test.device,
|
|
Dest: test.dest,
|
|
}
|
|
pm.ReleasePort(portRes)
|
|
continue
|
|
}
|
|
portRes := Reservation{
|
|
Networks: net,
|
|
Transport: fakeTransNumber,
|
|
Addr: test.ip,
|
|
Port: test.port,
|
|
Flags: test.flags,
|
|
BindToDevice: test.device,
|
|
Dest: test.dest,
|
|
}
|
|
gotPort, err := pm.ReservePort(rng, portRes, nil /* testPort */)
|
|
if diff := cmp.Diff(test.want, err); diff != "" {
|
|
t.Fatalf("unexpected error from ReservePort(%+v, _), (-want, +got):\n%s", portRes, diff)
|
|
}
|
|
if test.port == 0 && (gotPort == 0 || gotPort < first) {
|
|
t.Fatalf("ReservePort(%+v, _) = %d, want port number >= %d to be picked", portRes, gotPort, first)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPickEphemeralPort(t *testing.T) {
|
|
const (
|
|
firstEphemeral = 32000
|
|
numEphemeralPorts = 1000
|
|
)
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
f func(port uint16) (bool, tcpip.Error)
|
|
wantErr tcpip.Error
|
|
wantPort uint16
|
|
}{
|
|
{
|
|
name: "no-port-available",
|
|
f: func(port uint16) (bool, tcpip.Error) {
|
|
return false, nil
|
|
},
|
|
wantErr: &tcpip.ErrNoPortAvailable{},
|
|
},
|
|
{
|
|
name: "port-tester-error",
|
|
f: func(port uint16) (bool, tcpip.Error) {
|
|
return false, &tcpip.ErrBadBuffer{}
|
|
},
|
|
wantErr: &tcpip.ErrBadBuffer{},
|
|
},
|
|
{
|
|
name: "only-port-16042-available",
|
|
f: func(port uint16) (bool, tcpip.Error) {
|
|
if port == firstEphemeral+42 {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
},
|
|
wantPort: firstEphemeral + 42,
|
|
},
|
|
{
|
|
name: "only-port-under-16000-available",
|
|
f: func(port uint16) (bool, tcpip.Error) {
|
|
if port < firstEphemeral {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
},
|
|
wantErr: &tcpip.ErrNoPortAvailable{},
|
|
},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
pm := NewPortManager()
|
|
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
if err := pm.SetPortRange(firstEphemeral, firstEphemeral+numEphemeralPorts); err != nil {
|
|
t.Fatalf("failed to set ephemeral port range: %s", err)
|
|
}
|
|
port, err := pm.PickEphemeralPort(rng, test.f)
|
|
if diff := cmp.Diff(test.wantErr, err); diff != "" {
|
|
t.Fatalf("unexpected error from PickEphemeralPort(..), (-want, +got):\n%s", diff)
|
|
}
|
|
if port != test.wantPort {
|
|
t.Errorf("got PickEphemeralPort(..) = (%d, nil); want (%d, nil)", port, test.wantPort)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPickEphemeralPortStable(t *testing.T) {
|
|
const (
|
|
firstEphemeral = 32000
|
|
numEphemeralPorts = 1000
|
|
)
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
f func(port uint16) (bool, tcpip.Error)
|
|
wantErr tcpip.Error
|
|
wantPort uint16
|
|
}{
|
|
{
|
|
name: "no-port-available",
|
|
f: func(port uint16) (bool, tcpip.Error) {
|
|
return false, nil
|
|
},
|
|
wantErr: &tcpip.ErrNoPortAvailable{},
|
|
},
|
|
{
|
|
name: "port-tester-error",
|
|
f: func(port uint16) (bool, tcpip.Error) {
|
|
return false, &tcpip.ErrBadBuffer{}
|
|
},
|
|
wantErr: &tcpip.ErrBadBuffer{},
|
|
},
|
|
{
|
|
name: "only-port-16042-available",
|
|
f: func(port uint16) (bool, tcpip.Error) {
|
|
if port == firstEphemeral+42 {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
},
|
|
wantPort: firstEphemeral + 42,
|
|
},
|
|
{
|
|
name: "only-port-under-16000-available",
|
|
f: func(port uint16) (bool, tcpip.Error) {
|
|
if port < firstEphemeral {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
},
|
|
wantErr: &tcpip.ErrNoPortAvailable{},
|
|
},
|
|
} {
|
|
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)
|
|
}
|
|
portOffset := uint32(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)
|
|
}
|
|
if port != test.wantPort {
|
|
t.Errorf("got PickEphemeralPort(..) = (%d, nil); want (%d, nil)", port, test.wantPort)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestOverflow addresses b/183593432, wherein an overflowing uint16 causes a
|
|
// port allocation failure.
|
|
func TestOverflow(t *testing.T) {
|
|
// Use a small range and start at offsets that will cause an overflow.
|
|
count := uint16(50)
|
|
for offset := uint32(math.MaxUint16 - count); offset < math.MaxUint16; offset++ {
|
|
reservedPorts := make(map[uint16]struct{})
|
|
// Ensure we can reserve everything in the allowed range.
|
|
for i := uint16(0); i < count; i++ {
|
|
port, err := pickEphemeralPort(offset, firstEphemeral, count, func(port uint16) (bool, tcpip.Error) {
|
|
if _, ok := reservedPorts[port]; !ok {
|
|
reservedPorts[port] = struct{}{}
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("port picking failed at iteration %d, for offset %d, len(reserved): %+v", i, offset, len(reservedPorts))
|
|
}
|
|
if port < firstEphemeral || port > firstEphemeral+count {
|
|
t.Fatalf("reserved port %d, which is not in range [%d, %d]", port, firstEphemeral, firstEphemeral+count-1)
|
|
}
|
|
}
|
|
}
|
|
}
|