github.com/vpnishe/netstack@v1.10.6/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/rand"
    19  	"testing"
    20  
    21  	"github.com/vpnishe/netstack/tcpip"
    22  )
    23  
    24  const (
    25  	fakeTransNumber   tcpip.TransportProtocolNumber = 1
    26  	fakeNetworkNumber tcpip.NetworkProtocolNumber   = 2
    27  
    28  	fakeIPAddress  = tcpip.Address("\x08\x08\x08\x08")
    29  	fakeIPAddress1 = tcpip.Address("\x08\x08\x08\x09")
    30  )
    31  
    32  type portReserveTestAction struct {
    33  	port    uint16
    34  	ip      tcpip.Address
    35  	want    *tcpip.Error
    36  	reuse   bool
    37  	release bool
    38  	device  tcpip.NICID
    39  }
    40  
    41  func TestPortReservation(t *testing.T) {
    42  	for _, test := range []struct {
    43  		tname   string
    44  		actions []portReserveTestAction
    45  	}{
    46  		{
    47  			tname: "bind to ip",
    48  			actions: []portReserveTestAction{
    49  				{port: 80, ip: fakeIPAddress, want: nil},
    50  				{port: 80, ip: fakeIPAddress1, want: nil},
    51  				/* N.B. Order of tests matters! */
    52  				{port: 80, ip: anyIPAddress, want: tcpip.ErrPortInUse},
    53  				{port: 80, ip: fakeIPAddress, want: tcpip.ErrPortInUse, reuse: true},
    54  			},
    55  		},
    56  		{
    57  			tname: "bind to inaddr any",
    58  			actions: []portReserveTestAction{
    59  				{port: 22, ip: anyIPAddress, want: nil},
    60  				{port: 22, ip: fakeIPAddress, want: tcpip.ErrPortInUse},
    61  				/* release fakeIPAddress, but anyIPAddress is still inuse */
    62  				{port: 22, ip: fakeIPAddress, release: true},
    63  				{port: 22, ip: fakeIPAddress, want: tcpip.ErrPortInUse},
    64  				{port: 22, ip: fakeIPAddress, want: tcpip.ErrPortInUse, reuse: true},
    65  				/* Release port 22 from any IP address, then try to reserve fake IP address on 22 */
    66  				{port: 22, ip: anyIPAddress, want: nil, release: true},
    67  				{port: 22, ip: fakeIPAddress, want: nil},
    68  			},
    69  		}, {
    70  			tname: "bind to zero port",
    71  			actions: []portReserveTestAction{
    72  				{port: 00, ip: fakeIPAddress, want: nil},
    73  				{port: 00, ip: fakeIPAddress, want: nil},
    74  				{port: 00, ip: fakeIPAddress, reuse: true, want: nil},
    75  			},
    76  		}, {
    77  			tname: "bind to ip with reuseport",
    78  			actions: []portReserveTestAction{
    79  				{port: 25, ip: fakeIPAddress, reuse: true, want: nil},
    80  				{port: 25, ip: fakeIPAddress, reuse: true, want: nil},
    81  
    82  				{port: 25, ip: fakeIPAddress, reuse: false, want: tcpip.ErrPortInUse},
    83  				{port: 25, ip: anyIPAddress, reuse: false, want: tcpip.ErrPortInUse},
    84  
    85  				{port: 25, ip: anyIPAddress, reuse: true, want: nil},
    86  			},
    87  		}, {
    88  			tname: "bind to inaddr any with reuseport",
    89  			actions: []portReserveTestAction{
    90  				{port: 24, ip: anyIPAddress, reuse: true, want: nil},
    91  				{port: 24, ip: anyIPAddress, reuse: true, want: nil},
    92  
    93  				{port: 24, ip: anyIPAddress, reuse: false, want: tcpip.ErrPortInUse},
    94  				{port: 24, ip: fakeIPAddress, reuse: false, want: tcpip.ErrPortInUse},
    95  
    96  				{port: 24, ip: fakeIPAddress, reuse: true, want: nil},
    97  				{port: 24, ip: fakeIPAddress, release: true, want: nil},
    98  
    99  				{port: 24, ip: anyIPAddress, release: true},
   100  				{port: 24, ip: anyIPAddress, reuse: false, want: tcpip.ErrPortInUse},
   101  
   102  				{port: 24, ip: anyIPAddress, release: true},
   103  				{port: 24, ip: anyIPAddress, reuse: false, want: nil},
   104  			},
   105  		}, {
   106  			tname: "bind twice with device fails",
   107  			actions: []portReserveTestAction{
   108  				{port: 24, ip: fakeIPAddress, device: 3, want: nil},
   109  				{port: 24, ip: fakeIPAddress, device: 3, want: tcpip.ErrPortInUse},
   110  			},
   111  		}, {
   112  			tname: "bind to device",
   113  			actions: []portReserveTestAction{
   114  				{port: 24, ip: fakeIPAddress, device: 1, want: nil},
   115  				{port: 24, ip: fakeIPAddress, device: 2, want: nil},
   116  			},
   117  		}, {
   118  			tname: "bind to device and then without device",
   119  			actions: []portReserveTestAction{
   120  				{port: 24, ip: fakeIPAddress, device: 123, want: nil},
   121  				{port: 24, ip: fakeIPAddress, device: 0, want: tcpip.ErrPortInUse},
   122  			},
   123  		}, {
   124  			tname: "bind without device",
   125  			actions: []portReserveTestAction{
   126  				{port: 24, ip: fakeIPAddress, want: nil},
   127  				{port: 24, ip: fakeIPAddress, device: 123, want: tcpip.ErrPortInUse},
   128  				{port: 24, ip: fakeIPAddress, device: 123, reuse: true, want: tcpip.ErrPortInUse},
   129  				{port: 24, ip: fakeIPAddress, want: tcpip.ErrPortInUse},
   130  				{port: 24, ip: fakeIPAddress, reuse: true, want: tcpip.ErrPortInUse},
   131  			},
   132  		}, {
   133  			tname: "bind with device",
   134  			actions: []portReserveTestAction{
   135  				{port: 24, ip: fakeIPAddress, device: 123, want: nil},
   136  				{port: 24, ip: fakeIPAddress, device: 123, want: tcpip.ErrPortInUse},
   137  				{port: 24, ip: fakeIPAddress, device: 123, reuse: true, want: tcpip.ErrPortInUse},
   138  				{port: 24, ip: fakeIPAddress, device: 0, want: tcpip.ErrPortInUse},
   139  				{port: 24, ip: fakeIPAddress, device: 0, reuse: true, want: tcpip.ErrPortInUse},
   140  				{port: 24, ip: fakeIPAddress, device: 456, reuse: true, want: nil},
   141  				{port: 24, ip: fakeIPAddress, device: 789, want: nil},
   142  				{port: 24, ip: fakeIPAddress, want: tcpip.ErrPortInUse},
   143  				{port: 24, ip: fakeIPAddress, reuse: true, want: tcpip.ErrPortInUse},
   144  			},
   145  		}, {
   146  			tname: "bind with reuse",
   147  			actions: []portReserveTestAction{
   148  				{port: 24, ip: fakeIPAddress, reuse: true, want: nil},
   149  				{port: 24, ip: fakeIPAddress, device: 123, want: tcpip.ErrPortInUse},
   150  				{port: 24, ip: fakeIPAddress, device: 123, reuse: true, want: nil},
   151  				{port: 24, ip: fakeIPAddress, device: 0, want: tcpip.ErrPortInUse},
   152  				{port: 24, ip: fakeIPAddress, device: 0, reuse: true, want: nil},
   153  			},
   154  		}, {
   155  			tname: "binding with reuse and device",
   156  			actions: []portReserveTestAction{
   157  				{port: 24, ip: fakeIPAddress, device: 123, reuse: true, want: nil},
   158  				{port: 24, ip: fakeIPAddress, device: 123, want: tcpip.ErrPortInUse},
   159  				{port: 24, ip: fakeIPAddress, device: 123, reuse: true, want: nil},
   160  				{port: 24, ip: fakeIPAddress, device: 0, want: tcpip.ErrPortInUse},
   161  				{port: 24, ip: fakeIPAddress, device: 456, reuse: true, want: nil},
   162  				{port: 24, ip: fakeIPAddress, device: 0, reuse: true, want: nil},
   163  				{port: 24, ip: fakeIPAddress, device: 789, reuse: true, want: nil},
   164  				{port: 24, ip: fakeIPAddress, device: 999, want: tcpip.ErrPortInUse},
   165  			},
   166  		}, {
   167  			tname: "mixing reuse and not reuse by binding to device",
   168  			actions: []portReserveTestAction{
   169  				{port: 24, ip: fakeIPAddress, device: 123, reuse: true, want: nil},
   170  				{port: 24, ip: fakeIPAddress, device: 456, want: nil},
   171  				{port: 24, ip: fakeIPAddress, device: 789, reuse: true, want: nil},
   172  				{port: 24, ip: fakeIPAddress, device: 999, want: nil},
   173  			},
   174  		}, {
   175  			tname: "can't bind to 0 after mixing reuse and not reuse",
   176  			actions: []portReserveTestAction{
   177  				{port: 24, ip: fakeIPAddress, device: 123, reuse: true, want: nil},
   178  				{port: 24, ip: fakeIPAddress, device: 456, want: nil},
   179  				{port: 24, ip: fakeIPAddress, device: 0, reuse: true, want: tcpip.ErrPortInUse},
   180  			},
   181  		}, {
   182  			tname: "bind and release",
   183  			actions: []portReserveTestAction{
   184  				{port: 24, ip: fakeIPAddress, device: 123, reuse: true, want: nil},
   185  				{port: 24, ip: fakeIPAddress, device: 0, reuse: true, want: nil},
   186  				{port: 24, ip: fakeIPAddress, device: 345, reuse: false, want: tcpip.ErrPortInUse},
   187  				{port: 24, ip: fakeIPAddress, device: 789, reuse: true, want: nil},
   188  
   189  				// Release the bind to device 0 and try again.
   190  				{port: 24, ip: fakeIPAddress, device: 0, reuse: true, want: nil, release: true},
   191  				{port: 24, ip: fakeIPAddress, device: 345, reuse: false, want: nil},
   192  			},
   193  		}, {
   194  			tname: "bind twice with reuse once",
   195  			actions: []portReserveTestAction{
   196  				{port: 24, ip: fakeIPAddress, device: 123, reuse: false, want: nil},
   197  				{port: 24, ip: fakeIPAddress, device: 0, reuse: true, want: tcpip.ErrPortInUse},
   198  			},
   199  		}, {
   200  			tname: "release an unreserved device",
   201  			actions: []portReserveTestAction{
   202  				{port: 24, ip: fakeIPAddress, device: 123, reuse: false, want: nil},
   203  				{port: 24, ip: fakeIPAddress, device: 456, reuse: false, want: nil},
   204  				// The below don't exist.
   205  				{port: 24, ip: fakeIPAddress, device: 345, reuse: false, want: nil, release: true},
   206  				{port: 9999, ip: fakeIPAddress, device: 123, reuse: false, want: nil, release: true},
   207  				// Release all.
   208  				{port: 24, ip: fakeIPAddress, device: 123, reuse: false, want: nil, release: true},
   209  				{port: 24, ip: fakeIPAddress, device: 456, reuse: false, want: nil, release: true},
   210  			},
   211  		},
   212  	} {
   213  		t.Run(test.tname, func(t *testing.T) {
   214  			pm := NewPortManager()
   215  			net := []tcpip.NetworkProtocolNumber{fakeNetworkNumber}
   216  
   217  			for _, test := range test.actions {
   218  				if test.release {
   219  					pm.ReleasePort(net, fakeTransNumber, test.ip, test.port, test.device)
   220  					continue
   221  				}
   222  				gotPort, err := pm.ReservePort(net, fakeTransNumber, test.ip, test.port, test.reuse, test.device)
   223  				if err != test.want {
   224  					t.Fatalf("ReservePort(.., .., %s, %d, %t, %d) = %v, want %v", test.ip, test.port, test.reuse, test.device, err, test.want)
   225  				}
   226  				if test.port == 0 && (gotPort == 0 || gotPort < FirstEphemeral) {
   227  					t.Fatalf("ReservePort(.., .., .., 0) = %d, want port number >= %d to be picked", gotPort, FirstEphemeral)
   228  				}
   229  			}
   230  		})
   231  
   232  	}
   233  }
   234  
   235  func TestPickEphemeralPort(t *testing.T) {
   236  	customErr := &tcpip.Error{}
   237  	for _, test := range []struct {
   238  		name     string
   239  		f        func(port uint16) (bool, *tcpip.Error)
   240  		wantErr  *tcpip.Error
   241  		wantPort uint16
   242  	}{
   243  		{
   244  			name: "no-port-available",
   245  			f: func(port uint16) (bool, *tcpip.Error) {
   246  				return false, nil
   247  			},
   248  			wantErr: tcpip.ErrNoPortAvailable,
   249  		},
   250  		{
   251  			name: "port-tester-error",
   252  			f: func(port uint16) (bool, *tcpip.Error) {
   253  				return false, customErr
   254  			},
   255  			wantErr: customErr,
   256  		},
   257  		{
   258  			name: "only-port-16042-available",
   259  			f: func(port uint16) (bool, *tcpip.Error) {
   260  				if port == FirstEphemeral+42 {
   261  					return true, nil
   262  				}
   263  				return false, nil
   264  			},
   265  			wantPort: FirstEphemeral + 42,
   266  		},
   267  		{
   268  			name: "only-port-under-16000-available",
   269  			f: func(port uint16) (bool, *tcpip.Error) {
   270  				if port < FirstEphemeral {
   271  					return true, nil
   272  				}
   273  				return false, nil
   274  			},
   275  			wantErr: tcpip.ErrNoPortAvailable,
   276  		},
   277  	} {
   278  		t.Run(test.name, func(t *testing.T) {
   279  			pm := NewPortManager()
   280  			if port, err := pm.PickEphemeralPort(test.f); port != test.wantPort || err != test.wantErr {
   281  				t.Errorf("PickEphemeralPort(..) = (port %d, err %v); want (port %d, err %v)", port, err, test.wantPort, test.wantErr)
   282  			}
   283  		})
   284  	}
   285  }
   286  
   287  func TestPickEphemeralPortStable(t *testing.T) {
   288  	customErr := &tcpip.Error{}
   289  	for _, test := range []struct {
   290  		name     string
   291  		f        func(port uint16) (bool, *tcpip.Error)
   292  		wantErr  *tcpip.Error
   293  		wantPort uint16
   294  	}{
   295  		{
   296  			name: "no-port-available",
   297  			f: func(port uint16) (bool, *tcpip.Error) {
   298  				return false, nil
   299  			},
   300  			wantErr: tcpip.ErrNoPortAvailable,
   301  		},
   302  		{
   303  			name: "port-tester-error",
   304  			f: func(port uint16) (bool, *tcpip.Error) {
   305  				return false, customErr
   306  			},
   307  			wantErr: customErr,
   308  		},
   309  		{
   310  			name: "only-port-16042-available",
   311  			f: func(port uint16) (bool, *tcpip.Error) {
   312  				if port == FirstEphemeral+42 {
   313  					return true, nil
   314  				}
   315  				return false, nil
   316  			},
   317  			wantPort: FirstEphemeral + 42,
   318  		},
   319  		{
   320  			name: "only-port-under-16000-available",
   321  			f: func(port uint16) (bool, *tcpip.Error) {
   322  				if port < FirstEphemeral {
   323  					return true, nil
   324  				}
   325  				return false, nil
   326  			},
   327  			wantErr: tcpip.ErrNoPortAvailable,
   328  		},
   329  	} {
   330  		t.Run(test.name, func(t *testing.T) {
   331  			pm := NewPortManager()
   332  			portOffset := uint32(rand.Int31n(int32(numEphemeralPorts)))
   333  			if port, err := pm.PickEphemeralPortStable(portOffset, test.f); port != test.wantPort || err != test.wantErr {
   334  				t.Errorf("PickEphemeralPort(..) = (port %d, err %v); want (port %d, err %v)", port, err, test.wantPort, test.wantErr)
   335  			}
   336  		})
   337  	}
   338  }