github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/network/portrange_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package network_test
     5  
     6  import (
     7  	jc "github.com/juju/testing/checkers"
     8  	gc "gopkg.in/check.v1"
     9  
    10  	"github.com/juju/juju/core/network"
    11  	"github.com/juju/juju/testing"
    12  )
    13  
    14  type PortRangeSuite struct {
    15  	testing.BaseSuite
    16  }
    17  
    18  var _ = gc.Suite(&PortRangeSuite{})
    19  
    20  func (*PortRangeSuite) TestConflictsWith(c *gc.C) {
    21  	var testCases = []struct {
    22  		about          string
    23  		first          network.PortRange
    24  		second         network.PortRange
    25  		expectConflict bool
    26  	}{{
    27  		"identical ports",
    28  		network.PortRange{80, 80, "TCP"},
    29  		network.PortRange{80, 80, "TCP"},
    30  		true,
    31  	}, {
    32  		"different ports",
    33  		network.PortRange{80, 80, "TCP"},
    34  		network.PortRange{90, 90, "TCP"},
    35  		false,
    36  	}, {
    37  		"touching ranges",
    38  		network.PortRange{100, 200, "TCP"},
    39  		network.PortRange{201, 240, "TCP"},
    40  		false,
    41  	}, {
    42  		"touching ranges with overlap",
    43  		network.PortRange{100, 200, "TCP"},
    44  		network.PortRange{200, 240, "TCP"},
    45  		true,
    46  	}, {
    47  		"different protocols",
    48  		network.PortRange{80, 80, "UDP"},
    49  		network.PortRange{80, 80, "TCP"},
    50  		false,
    51  	}, {
    52  		"outside range",
    53  		network.PortRange{100, 200, "TCP"},
    54  		network.PortRange{80, 80, "TCP"},
    55  		false,
    56  	}, {
    57  		"overlap end",
    58  		network.PortRange{100, 200, "TCP"},
    59  		network.PortRange{80, 120, "TCP"},
    60  		true,
    61  	}, {
    62  		"complete overlap",
    63  		network.PortRange{100, 200, "TCP"},
    64  		network.PortRange{120, 140, "TCP"},
    65  		true,
    66  	}}
    67  
    68  	for i, t := range testCases {
    69  		c.Logf("test %d: %s", i, t.about)
    70  		c.Check(t.first.ConflictsWith(t.second), gc.Equals, t.expectConflict)
    71  		c.Check(t.second.ConflictsWith(t.first), gc.Equals, t.expectConflict)
    72  	}
    73  }
    74  
    75  func (*PortRangeSuite) TestStrings(c *gc.C) {
    76  	c.Assert(
    77  		network.PortRange{80, 80, "TCP"}.String(),
    78  		gc.Equals,
    79  		"80/tcp",
    80  	)
    81  	c.Assert(
    82  		network.PortRange{80, 80, "TCP"}.GoString(),
    83  		gc.Equals,
    84  		"80/tcp",
    85  	)
    86  	c.Assert(
    87  		network.PortRange{80, 100, "TCP"}.String(),
    88  		gc.Equals,
    89  		"80-100/tcp",
    90  	)
    91  	c.Assert(
    92  		network.PortRange{80, 100, "TCP"}.GoString(),
    93  		gc.Equals,
    94  		"80-100/tcp",
    95  	)
    96  	c.Assert(
    97  		network.PortRange{-1, -1, "ICMP"}.String(),
    98  		gc.Equals,
    99  		"icmp",
   100  	)
   101  }
   102  
   103  func (*PortRangeSuite) TestValidate(c *gc.C) {
   104  	testCases := []struct {
   105  		about    string
   106  		ports    network.PortRange
   107  		expected string
   108  	}{{
   109  		"single valid port",
   110  		network.PortRange{80, 80, "tcp"},
   111  		"",
   112  	}, {
   113  		"valid port range",
   114  		network.PortRange{80, 90, "tcp"},
   115  		"",
   116  	}, {
   117  		"valid udp port range",
   118  		network.PortRange{80, 90, "UDP"},
   119  		"",
   120  	}, {
   121  		"invalid port range boundaries",
   122  		network.PortRange{90, 80, "tcp"},
   123  		"invalid port range 90-80/tcp",
   124  	}, {
   125  		"both FromPort and ToPort too large",
   126  		network.PortRange{88888, 99999, "tcp"},
   127  		"port range bounds must be between 0 and 65535, got 88888-99999",
   128  	}, {
   129  		"FromPort too large",
   130  		network.PortRange{88888, 65535, "tcp"},
   131  		"invalid port range 88888-65535/tcp",
   132  	}, {
   133  		"FromPort too small",
   134  		network.PortRange{-1, 80, "tcp"},
   135  		"port range bounds must be between 0 and 65535, got -1-80",
   136  	}, {
   137  		"ToPort too large",
   138  		network.PortRange{1, 99999, "tcp"},
   139  		"port range bounds must be between 0 and 65535, got 1-99999",
   140  	}, {
   141  		"invalid protocol",
   142  		network.PortRange{80, 80, "some protocol"},
   143  		`invalid protocol "some protocol", expected "tcp", "udp", or "icmp"`,
   144  	}, {
   145  		"invalid icmp port",
   146  		network.PortRange{1, 1, "icmp"},
   147  		`protocol "icmp" doesn't support any ports; got "1"`,
   148  	}}
   149  
   150  	for i, t := range testCases {
   151  		c.Logf("test %d: %s", i, t.about)
   152  		if t.expected == "" {
   153  			c.Check(t.ports.Validate(), gc.IsNil)
   154  		} else {
   155  			c.Check(t.ports.Validate(), gc.ErrorMatches, t.expected)
   156  		}
   157  	}
   158  }
   159  
   160  func (*PortRangeSuite) TestSortPortRanges(c *gc.C) {
   161  	ranges := []network.PortRange{
   162  		{10, 100, "udp"},
   163  		{80, 90, "tcp"},
   164  		{80, 80, "tcp"},
   165  	}
   166  	expected := []network.PortRange{
   167  		{80, 80, "tcp"},
   168  		{80, 90, "tcp"},
   169  		{10, 100, "udp"},
   170  	}
   171  	network.SortPortRanges(ranges)
   172  	c.Assert(ranges, gc.DeepEquals, expected)
   173  }
   174  
   175  func (*PortRangeSuite) TestParsePortRange(c *gc.C) {
   176  	portRange, err := network.ParsePortRange("8000-8099/tcp")
   177  	c.Assert(err, jc.ErrorIsNil)
   178  
   179  	c.Check(portRange.Protocol, gc.Equals, "tcp")
   180  	c.Check(portRange.FromPort, gc.Equals, 8000)
   181  	c.Check(portRange.ToPort, gc.Equals, 8099)
   182  }
   183  
   184  func (*PortRangeSuite) TestParsePortRangeSingle(c *gc.C) {
   185  	portRange, err := network.ParsePortRange("80/tcp")
   186  	c.Assert(err, jc.ErrorIsNil)
   187  
   188  	c.Check(portRange.Protocol, gc.Equals, "tcp")
   189  	c.Check(portRange.FromPort, gc.Equals, 80)
   190  	c.Check(portRange.ToPort, gc.Equals, 80)
   191  }
   192  
   193  func (*PortRangeSuite) TestParsePortRangeDefaultProtocol(c *gc.C) {
   194  	portRange, err := network.ParsePortRange("80")
   195  	c.Assert(err, jc.ErrorIsNil)
   196  
   197  	c.Check(portRange.Protocol, gc.Equals, "tcp")
   198  	c.Check(portRange.FromPort, gc.Equals, 80)
   199  	c.Check(portRange.ToPort, gc.Equals, 80)
   200  }
   201  
   202  func (*PortRangeSuite) TestParseIcmpProtocol(c *gc.C) {
   203  	portRange, err := network.ParsePortRange("icmp")
   204  	c.Assert(err, jc.ErrorIsNil)
   205  
   206  	c.Check(portRange.Protocol, gc.Equals, "icmp")
   207  	c.Check(portRange.FromPort, gc.Equals, -1)
   208  	c.Check(portRange.ToPort, gc.Equals, -1)
   209  }
   210  
   211  func (*PortRangeSuite) TestParseIcmpProtocolRoundTrip(c *gc.C) {
   212  	portRange, err := network.ParsePortRange("icmp")
   213  	c.Assert(err, jc.ErrorIsNil)
   214  	portRangeStr := portRange.String()
   215  
   216  	c.Check(portRangeStr, gc.Equals, "icmp")
   217  }
   218  
   219  func (*PortRangeSuite) TestParsePortRangeRoundTrip(c *gc.C) {
   220  	portRange, err := network.ParsePortRange("8000-8099/tcp")
   221  	c.Assert(err, jc.ErrorIsNil)
   222  	portRangeStr := portRange.String()
   223  
   224  	c.Check(portRangeStr, gc.Equals, "8000-8099/tcp")
   225  }
   226  
   227  func (*PortRangeSuite) TestParsePortRangeMultiRange(c *gc.C) {
   228  	_, err := network.ParsePortRange("10-55-100")
   229  
   230  	c.Check(err, gc.ErrorMatches, `invalid port range "10-55-100".*`)
   231  }
   232  
   233  func (*PortRangeSuite) TestParsePortRangeNonIntPort(c *gc.C) {
   234  	_, err := network.ParsePortRange("spam-100")
   235  
   236  	c.Check(err, gc.ErrorMatches, `invalid port "spam".*`)
   237  }
   238  
   239  func (*PortRangeSuite) TestMustParsePortRange(c *gc.C) {
   240  	portRange := network.MustParsePortRange("8000-8099/tcp")
   241  
   242  	c.Check(portRange.Protocol, gc.Equals, "tcp")
   243  	c.Check(portRange.FromPort, gc.Equals, 8000)
   244  	c.Check(portRange.ToPort, gc.Equals, 8099)
   245  }
   246  
   247  func (*PortRangeSuite) TestMustParsePortRangeInvalid(c *gc.C) {
   248  	f := func() {
   249  		network.MustParsePortRange("10-55-100")
   250  	}
   251  
   252  	c.Check(f, gc.PanicMatches, `invalid port range "10-55-100".*`)
   253  }
   254  
   255  func (*PortRangeSuite) TestCombinePortRanges(c *gc.C) {
   256  	testCases := []struct {
   257  		in       []network.PortRange
   258  		expected []network.PortRange
   259  	}{{
   260  		[]network.PortRange{{80, 80, "tcp"}},
   261  		[]network.PortRange{{80, 80, "tcp"}},
   262  	}, {
   263  		[]network.PortRange{{80, 82, "tcp"}, {83, 85, "tcp"}},
   264  		[]network.PortRange{{80, 85, "tcp"}},
   265  	}, {
   266  		[]network.PortRange{{83, 85, "tcp"}, {80, 82, "tcp"}},
   267  		[]network.PortRange{{80, 85, "tcp"}},
   268  	}, {
   269  		[]network.PortRange{{80, 83, "tcp"}, {85, 87, "tcp"}},
   270  		[]network.PortRange{{80, 83, "tcp"}, {85, 87, "tcp"}},
   271  	}, {
   272  		[]network.PortRange{{85, 87, "tcp"}, {80, 83, "tcp"}},
   273  		[]network.PortRange{{80, 83, "tcp"}, {85, 87, "tcp"}},
   274  	}, {
   275  		[]network.PortRange{{85, 87, "tcp"}, {80, 83, "tcp"}},
   276  		[]network.PortRange{{80, 83, "tcp"}, {85, 87, "tcp"}},
   277  	}, {
   278  		[]network.PortRange{{80, 83, "tcp"}, {84, 87, "udp"}},
   279  		[]network.PortRange{{80, 83, "tcp"}, {84, 87, "udp"}},
   280  	}, {
   281  		[]network.PortRange{{80, 82, "tcp"}, {80, 80, "udp"}, {83, 83, "tcp"}, {81, 84, "udp"}, {84, 85, "tcp"}},
   282  		[]network.PortRange{{80, 85, "tcp"}, {80, 84, "udp"}},
   283  	}, {
   284  		[]network.PortRange{{80, 82, "tcp"}, {81, 84, "udp"}, {84, 84, "tcp"}, {86, 87, "udp"}, {80, 80, "udp"}},
   285  		[]network.PortRange{{80, 82, "tcp"}, {84, 84, "tcp"}, {80, 84, "udp"}, {86, 87, "udp"}},
   286  	}}
   287  	for i, t := range testCases {
   288  		c.Logf("test %d", i)
   289  		c.Check(network.CombinePortRanges(t.in...), jc.DeepEquals, t.expected)
   290  	}
   291  }
   292  
   293  func (p *PortRangeSuite) TestPortRangeLength(c *gc.C) {
   294  	testCases := []struct {
   295  		about        string
   296  		ports        network.PortRange
   297  		expectLength int
   298  	}{{
   299  		"single valid port",
   300  		network.MustParsePortRange("80/tcp"),
   301  		1,
   302  	}, {
   303  		"tcp port range",
   304  		network.MustParsePortRange("80-90/tcp"),
   305  		11,
   306  	}, {
   307  		"udp port range",
   308  		network.MustParsePortRange("80-90/udp"),
   309  		11,
   310  	}, {
   311  		"ICMP range",
   312  		network.PortRange{Protocol: "icmp", FromPort: -1, ToPort: -1},
   313  		1,
   314  	}, {
   315  		"longest valid range",
   316  		network.MustParsePortRange("1-65535/tcp"),
   317  		65535,
   318  	}}
   319  
   320  	for i, t := range testCases {
   321  		c.Logf("test %d: %s", i, t.about)
   322  		c.Check(t.ports.Length(), gc.Equals, t.expectLength)
   323  	}
   324  }
   325  
   326  func (p *PortRangeSuite) TestSanitizeBounds(c *gc.C) {
   327  	tests := []struct {
   328  		about  string
   329  		input  network.PortRange
   330  		output network.PortRange
   331  	}{{
   332  		"valid range",
   333  		network.PortRange{FromPort: 100, ToPort: 200},
   334  		network.PortRange{FromPort: 100, ToPort: 200},
   335  	}, {
   336  		"negative lower bound",
   337  		network.PortRange{FromPort: -10, ToPort: 10},
   338  		network.PortRange{FromPort: 1, ToPort: 10},
   339  	}, {
   340  		"zero lower bound",
   341  		network.PortRange{FromPort: 0, ToPort: 10},
   342  		network.PortRange{FromPort: 1, ToPort: 10},
   343  	}, {
   344  		"negative upper bound",
   345  		network.PortRange{FromPort: 42, ToPort: -20},
   346  		network.PortRange{FromPort: 1, ToPort: 42},
   347  	}, {
   348  		"zero upper bound",
   349  		network.PortRange{FromPort: 42, ToPort: 0},
   350  		network.PortRange{FromPort: 1, ToPort: 42},
   351  	}, {
   352  		"both bounds negative",
   353  		network.PortRange{FromPort: -10, ToPort: -20},
   354  		network.PortRange{FromPort: 1, ToPort: 1},
   355  	}, {
   356  		"both bounds zero",
   357  		network.PortRange{FromPort: 0, ToPort: 0},
   358  		network.PortRange{FromPort: 1, ToPort: 1},
   359  	}, {
   360  		"swapped bounds",
   361  		network.PortRange{FromPort: 20, ToPort: 10},
   362  		network.PortRange{FromPort: 10, ToPort: 20},
   363  	}, {
   364  		"too large upper bound",
   365  		network.PortRange{FromPort: 20, ToPort: 99999},
   366  		network.PortRange{FromPort: 20, ToPort: 65535},
   367  	}, {
   368  		"too large lower bound",
   369  		network.PortRange{FromPort: 99999, ToPort: 10},
   370  		network.PortRange{FromPort: 10, ToPort: 65535},
   371  	}, {
   372  		"both bounds too large",
   373  		network.PortRange{FromPort: 88888, ToPort: 99999},
   374  		network.PortRange{FromPort: 65535, ToPort: 65535},
   375  	}, {
   376  		"lower negative, upper too large",
   377  		network.PortRange{FromPort: -10, ToPort: 99999},
   378  		network.PortRange{FromPort: 1, ToPort: 65535},
   379  	}, {
   380  		"lower zero, upper too large",
   381  		network.PortRange{FromPort: 0, ToPort: 99999},
   382  		network.PortRange{FromPort: 1, ToPort: 65535},
   383  	}}
   384  	for i, t := range tests {
   385  		c.Logf("test %d: %s", i, t.about)
   386  		c.Check(t.input.SanitizeBounds(), jc.DeepEquals, t.output)
   387  	}
   388  }
   389  
   390  func (p *PortRangeSuite) TestUniquePortRanges(c *gc.C) {
   391  	in := []network.PortRange{
   392  		network.MustParsePortRange("123/tcp"),
   393  		network.MustParsePortRange("123/tcp"),
   394  		network.MustParsePortRange("123/tcp"),
   395  		network.MustParsePortRange("456/tcp"),
   396  	}
   397  
   398  	exp := []network.PortRange{
   399  		network.MustParsePortRange("123/tcp"),
   400  		network.MustParsePortRange("456/tcp"),
   401  	}
   402  
   403  	got := network.UniquePortRanges(in)
   404  	c.Assert(got, gc.DeepEquals, exp, gc.Commentf("expected duplicate port ranges to be removed"))
   405  }
   406  
   407  func (p *PortRangeSuite) TestUniquePortRangesInGroup(c *gc.C) {
   408  	in := network.GroupedPortRanges{
   409  		"foxtrot": []network.PortRange{
   410  			network.MustParsePortRange("123/tcp"),
   411  			network.MustParsePortRange("123/tcp"),
   412  		},
   413  		"unicorn": []network.PortRange{
   414  			network.MustParsePortRange("123/tcp"),
   415  			network.MustParsePortRange("456/tcp"),
   416  		},
   417  	}
   418  
   419  	exp := []network.PortRange{
   420  		network.MustParsePortRange("123/tcp"),
   421  		network.MustParsePortRange("456/tcp"),
   422  	}
   423  
   424  	got := in.UniquePortRanges()
   425  	c.Assert(got, gc.DeepEquals, exp, gc.Commentf("expected duplicate port ranges to be removed"))
   426  }
   427  
   428  func (p *PortRangeSuite) TestGroupedPortRangesEquality(c *gc.C) {
   429  	specs := []struct {
   430  		descr    string
   431  		a, b     network.GroupedPortRanges
   432  		expEqual bool
   433  	}{
   434  		{
   435  			descr: "equal port ranges in random order",
   436  			a: network.GroupedPortRanges{
   437  				"foo": []network.PortRange{
   438  					network.MustParsePortRange("123/tcp"),
   439  					network.MustParsePortRange("456/tcp"),
   440  				},
   441  				"bar": []network.PortRange{
   442  					network.MustParsePortRange("123/tcp"),
   443  				},
   444  			},
   445  			b: network.GroupedPortRanges{
   446  				"foo": []network.PortRange{
   447  					network.MustParsePortRange("456/tcp"),
   448  					network.MustParsePortRange("123/tcp"),
   449  				},
   450  				"bar": []network.PortRange{
   451  					network.MustParsePortRange("123/tcp"),
   452  				},
   453  			},
   454  			expEqual: true,
   455  		},
   456  		{
   457  			descr: "groups with different lengths",
   458  			a: network.GroupedPortRanges{
   459  				"foo": []network.PortRange{
   460  					network.MustParsePortRange("123/tcp"),
   461  					network.MustParsePortRange("456/tcp"),
   462  				},
   463  			},
   464  			b: network.GroupedPortRanges{
   465  				"foo": []network.PortRange{
   466  					network.MustParsePortRange("123/tcp"),
   467  				},
   468  			},
   469  			expEqual: false,
   470  		},
   471  		{
   472  			descr: "groups with same length but different keys",
   473  			a: network.GroupedPortRanges{
   474  				"foo": []network.PortRange{
   475  					network.MustParsePortRange("123/tcp"),
   476  					network.MustParsePortRange("456/tcp"),
   477  				},
   478  			},
   479  			b: network.GroupedPortRanges{
   480  				"bar": []network.PortRange{
   481  					network.MustParsePortRange("123/tcp"),
   482  				},
   483  			},
   484  			expEqual: false,
   485  		},
   486  	}
   487  
   488  	for i, spec := range specs {
   489  		c.Logf("test %d: %s", i, spec.descr)
   490  		got := spec.a.EqualTo(spec.b)
   491  		c.Assert(got, gc.Equals, spec.expEqual)
   492  	}
   493  }