github.com/rootless-containers/rootlesskit/v2@v2.3.4/pkg/port/portutil/portutil_test.go (about)

     1  package portutil
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  
     7  	"github.com/rootless-containers/rootlesskit/v2/pkg/port"
     8  	"gotest.tools/v3/assert"
     9  )
    10  
    11  func TestParsePortSpec(t *testing.T) {
    12  	type testCase struct {
    13  		s string
    14  		// nil for invalid string
    15  		expected *port.Spec
    16  	}
    17  	testCases := []testCase{
    18  		{
    19  			s: "127.0.0.1:8080:80/tcp",
    20  			expected: &port.Spec{
    21  				Proto:      "tcp",
    22  				ParentIP:   "127.0.0.1",
    23  				ParentPort: 8080,
    24  				ChildPort:  80,
    25  			},
    26  		},
    27  		{
    28  			s: "127.0.0.1:8080:80/tcp4",
    29  			expected: &port.Spec{
    30  				Proto:      "tcp4",
    31  				ParentIP:   "127.0.0.1",
    32  				ParentPort: 8080,
    33  				ChildPort:  80,
    34  			},
    35  		},
    36  		{
    37  			s: "127.0.0.1:8080:10.0.2.100:80/tcp",
    38  			expected: &port.Spec{
    39  				Proto:      "tcp",
    40  				ParentIP:   "127.0.0.1",
    41  				ParentPort: 8080,
    42  				ChildIP:    "10.0.2.100",
    43  				ChildPort:  80,
    44  			},
    45  		},
    46  		{
    47  			s: "bad",
    48  		},
    49  		{
    50  			s: "127.0.0.1:8080:80/tcp,127.0.0.1:4040:40/tcp",
    51  			// one entry per one string
    52  		},
    53  		{
    54  			s: "8080",
    55  			// future version may support short formats like this
    56  		},
    57  		{
    58  			s: "[::1]:8080:80/tcp",
    59  			expected: &port.Spec{
    60  				Proto:      "tcp",
    61  				ParentIP:   "::1",
    62  				ParentPort: 8080,
    63  				ChildPort:  80,
    64  			},
    65  		},
    66  		{
    67  			s: "[::1]:8080:[::2]:80/udp",
    68  			expected: &port.Spec{
    69  				Proto:      "udp",
    70  				ParentIP:   "::1",
    71  				ParentPort: 8080,
    72  				ChildIP:    "::2",
    73  				ChildPort:  80,
    74  			},
    75  		},
    76  	}
    77  	for _, tc := range testCases {
    78  		tc := tc
    79  		t.Run(tc.s, func(t *testing.T) {
    80  			got, err := ParsePortSpec(tc.s)
    81  			if tc.expected == nil {
    82  				if err == nil {
    83  					t.Fatalf("error is expected for %q", tc.s)
    84  				}
    85  			} else {
    86  				if err != nil {
    87  					t.Fatalf("got error for %q: %v", tc.s, err)
    88  				}
    89  				if !reflect.DeepEqual(got, tc.expected) {
    90  					t.Fatalf("expected %+v, got %+v", tc.expected, got)
    91  				}
    92  			}
    93  		})
    94  	}
    95  }
    96  
    97  func TestValidatePortSpec(t *testing.T) {
    98  	existingPorts := make(map[int]*port.Status)
    99  
   100  	// bind to all host IPs
   101  	existingPorts[1] = &port.Status{
   102  		ID: 1,
   103  		Spec: port.Spec{
   104  			Proto:      "tcp",
   105  			ParentIP:   "",
   106  			ParentPort: 80,
   107  			ChildPort:  80,
   108  		},
   109  	}
   110  	// bind to only host IP 10.10.10.10
   111  	existingPorts[2] = &port.Status{
   112  		ID: 2,
   113  		Spec: port.Spec{
   114  			Proto:      "tcp",
   115  			ParentIP:   "10.10.10.10",
   116  			ParentPort: 8080,
   117  			ChildPort:  8080,
   118  		},
   119  	}
   120  	// avoid typing the spec over and over for small changes
   121  	spec := port.Spec{
   122  		Proto:      "tcp",
   123  		ParentIP:   "127.0.0.1",
   124  		ParentPort: 1001,
   125  		ChildPort:  1001,
   126  	}
   127  
   128  	// proto must be supplied and must equal "udp" or "tcp"
   129  	invalidProtos := []string{"", "NaN", "TCP"}
   130  	validProtos := []string{"udp", "tcp", "sctp"}
   131  	for _, p := range invalidProtos {
   132  		s := spec
   133  		s.Proto = p
   134  		err := ValidatePortSpec(s, existingPorts)
   135  		assert.ErrorContains(t, err, "unknown proto")
   136  	}
   137  	for _, p := range validProtos {
   138  		s := spec
   139  		s.Proto = p
   140  		err := ValidatePortSpec(s, existingPorts)
   141  		assert.NilError(t, err)
   142  
   143  	}
   144  
   145  	s := port.Spec{Proto: "tcp", ParentIP: "invalid", ParentPort: 80, ChildPort: 80}
   146  	assert.ErrorContains(t, ValidatePortSpec(s, existingPorts), "invalid ParentIP")
   147  
   148  	s = port.Spec{Proto: "tcp", ParentPort: 80, ChildIP: "invalid", ChildPort: 80}
   149  	assert.ErrorContains(t, ValidatePortSpec(s, existingPorts), "invalid ChildIP")
   150  
   151  	invalidPorts := []int{-200, 0, 1000000}
   152  	validPorts := []int{20, 500, 1337, 65000}
   153  
   154  	// 0 < parentPort <= 65535
   155  	for _, p := range invalidPorts {
   156  		s := spec
   157  		s.ParentPort = p
   158  		err := ValidatePortSpec(s, existingPorts)
   159  		assert.ErrorContains(t, err, "invalid ParentPort")
   160  	}
   161  	for _, p := range validPorts {
   162  		s := spec
   163  		s.ParentPort = p
   164  		err := ValidatePortSpec(s, existingPorts)
   165  		assert.NilError(t, err)
   166  	}
   167  
   168  	// 0 < childPort <= 65535
   169  	for _, p := range invalidPorts {
   170  		s := spec
   171  		s.ChildPort = p
   172  		err := ValidatePortSpec(s, existingPorts)
   173  		assert.ErrorContains(t, err, "invalid ChildPort")
   174  	}
   175  	for _, p := range validPorts {
   176  		s := spec
   177  		s.ChildPort = p
   178  		err := ValidatePortSpec(s, existingPorts)
   179  		assert.NilError(t, err)
   180  	}
   181  
   182  	// ChildPorts can overlap so long as parent port/IPs don't
   183  	// existing ports include tcp 10.10.10.10:8080, tcp *:80, no udp
   184  
   185  	// udp doesn't conflict with tcp
   186  	s = port.Spec{Proto: "udp", ParentPort: 80, ChildPort: 80}
   187  	assert.NilError(t, ValidatePortSpec(s, existingPorts))
   188  
   189  	// same parent, same child, different IP has no conflict
   190  	s = port.Spec{Proto: "tcp", ParentIP: "10.10.10.11", ParentPort: 8080, ChildPort: 8080}
   191  	assert.NilError(t, ValidatePortSpec(s, existingPorts))
   192  
   193  	// same IP different parentPort, same child port has no conflict
   194  	s = port.Spec{Proto: "tcp", ParentIP: "10.10.10.10", ParentPort: 8081, ChildPort: 8080}
   195  	assert.NilError(t, ValidatePortSpec(s, existingPorts))
   196  
   197  	// Same parent IP and Port should conflict, even if child port different
   198  	// conflict with ID 1:
   199  	s = port.Spec{Proto: "tcp", ParentPort: 80, ChildPort: 90}
   200  	err := ValidatePortSpec(s, existingPorts)
   201  	assert.Error(t, err, "conflict with ID 1")
   202  
   203  	// conflict with ID 2
   204  	s = port.Spec{Proto: "tcp", ParentIP: "10.10.10.10", ParentPort: 8080, ChildPort: 8080}
   205  	err = ValidatePortSpec(s, existingPorts)
   206  	assert.Error(t, err, "conflict with ID 2")
   207  }