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

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package network_test
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"sort"
    10  
    11  	"github.com/juju/errors"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    14  
    15  	"github.com/juju/juju/core/network"
    16  	"github.com/juju/juju/testing"
    17  )
    18  
    19  type AddressSuite struct {
    20  	testing.BaseSuite
    21  }
    22  
    23  var _ = gc.Suite(&AddressSuite{})
    24  
    25  func (s *AddressSuite) TestNewScopedAddressIPv4(c *gc.C) {
    26  	type test struct {
    27  		value         string
    28  		scope         network.Scope
    29  		expectedScope network.Scope
    30  	}
    31  
    32  	tests := []test{{
    33  		value:         "127.0.0.1",
    34  		scope:         network.ScopeUnknown,
    35  		expectedScope: network.ScopeMachineLocal,
    36  	}, {
    37  		value:         "127.0.0.1",
    38  		scope:         network.ScopePublic,
    39  		expectedScope: network.ScopePublic, // don't second guess != Unknown
    40  	}, {
    41  		value:         "10.0.3.1",
    42  		scope:         network.ScopeUnknown,
    43  		expectedScope: network.ScopeCloudLocal,
    44  	}, {
    45  		value:         "172.16.15.14",
    46  		scope:         network.ScopeUnknown,
    47  		expectedScope: network.ScopeCloudLocal,
    48  	}, {
    49  		value:         "192.168.0.1",
    50  		scope:         network.ScopeUnknown,
    51  		expectedScope: network.ScopeCloudLocal,
    52  	}, {
    53  		value:         "169.254.1.1",
    54  		scope:         network.ScopeUnknown,
    55  		expectedScope: network.ScopeLinkLocal,
    56  	}, {
    57  		value:         "8.8.8.8",
    58  		scope:         network.ScopeUnknown,
    59  		expectedScope: network.ScopePublic,
    60  	}, {
    61  		value:         "241.1.2.3",
    62  		scope:         network.ScopeUnknown,
    63  		expectedScope: network.ScopeFanLocal,
    64  	}}
    65  
    66  	for i, t := range tests {
    67  		c.Logf("test %d: %s %s", i, t.value, t.scope)
    68  		addr := network.NewSpaceAddress(t.value, network.WithScope(t.scope))
    69  		c.Check(addr.Value, gc.Equals, t.value)
    70  		c.Check(addr.Type, gc.Equals, network.IPv4Address)
    71  		c.Check(addr.Scope, gc.Equals, t.expectedScope)
    72  	}
    73  }
    74  
    75  func (s *AddressSuite) TestNewScopedAddressIPv6(c *gc.C) {
    76  	// Examples below taken from
    77  	// http://en.wikipedia.org/wiki/IPv6_address
    78  	testAddresses := []struct {
    79  		value string
    80  		scope network.Scope
    81  	}{
    82  		// IPv6 loopback address
    83  		{"::1", network.ScopeMachineLocal},
    84  		// used documentation examples
    85  		{"2001:db8::1", network.ScopePublic},
    86  		// link-local
    87  		{"fe80::1", network.ScopeLinkLocal},
    88  		// unique local address (ULA) - first group
    89  		{"fc00::1", network.ScopeCloudLocal},
    90  		// unique local address (ULA) - second group
    91  		{"fd00::1", network.ScopeCloudLocal},
    92  		// IPv4-mapped IPv6 address
    93  		{"::ffff:0:0:1", network.ScopePublic},
    94  		// IPv4-translated IPv6 address (SIIT)
    95  		{"::ffff:0:0:0:1", network.ScopePublic},
    96  		// "well-known" prefix for IPv4/IPv6 auto translation
    97  		{"64:ff9b::1", network.ScopePublic},
    98  		// used for 6to4 addressing
    99  		{"2002::1", network.ScopePublic},
   100  		// used for Teredo tunneling
   101  		{"2001::1", network.ScopePublic},
   102  		// used for IPv6 benchmarking
   103  		{"2001:2::1", network.ScopePublic},
   104  		// used for cryptographic hash identifiers
   105  		{"2001:10::1", network.ScopePublic},
   106  		// interface-local multicast (all nodes)
   107  		{"ff01::1", network.ScopeLinkLocal},
   108  		// link-local multicast (all nodes)
   109  		{"ff02::1", network.ScopeLinkLocal},
   110  		// interface-local multicast (all routers)
   111  		{"ff01::2", network.ScopeLinkLocal},
   112  		// link-local multicast (all routers)
   113  		{"ff02::2", network.ScopeLinkLocal},
   114  	}
   115  	for i, test := range testAddresses {
   116  		c.Logf("test %d: %q -> %q", i, test.value, test.scope)
   117  		addr := network.NewSpaceAddress(test.value)
   118  		c.Check(addr.Value, gc.Equals, test.value)
   119  		c.Check(addr.Type, gc.Equals, network.IPv6Address)
   120  		c.Check(addr.Scope, gc.Equals, test.scope)
   121  	}
   122  }
   123  
   124  func (s *AddressSuite) TestAsProviderAddress(c *gc.C) {
   125  	addr1 := network.NewMachineAddress("0.1.2.3").AsProviderAddress(
   126  		network.WithSpaceName("foo"),
   127  		network.WithProviderSpaceID("3"),
   128  		network.WithProviderID("523"),
   129  		network.WithProviderSubnetID("5"),
   130  		network.WithProviderVLANID("5001"),
   131  		network.WithVLANTag(50),
   132  	)
   133  	addr2 := network.NewMachineAddress("2001:db8::123").AsProviderAddress(
   134  		network.WithSpaceName(""),
   135  	)
   136  	c.Check(addr1, jc.DeepEquals, network.ProviderAddress{
   137  		MachineAddress: network.MachineAddress{
   138  			Value: "0.1.2.3",
   139  			Type:  "ipv4",
   140  			Scope: "public",
   141  		},
   142  		SpaceName:        "foo",
   143  		ProviderSpaceID:  "3",
   144  		ProviderID:       "523",
   145  		ProviderSubnetID: "5",
   146  		ProviderVLANID:   "5001",
   147  		VLANTag:          50,
   148  	})
   149  	c.Check(addr2, jc.DeepEquals, network.ProviderAddress{
   150  		MachineAddress: network.MachineAddress{
   151  			Value: "2001:db8::123",
   152  			Type:  "ipv6",
   153  			Scope: "public",
   154  		},
   155  		SpaceName: "",
   156  	})
   157  }
   158  
   159  func (s *AddressSuite) TestAsProviderAddresses(c *gc.C) {
   160  	addrs := network.NewMachineAddresses([]string{"0.2.3.4", "fc00::1"}).AsProviderAddresses(
   161  		network.WithSpaceName("bar"),
   162  		network.WithProviderSpaceID("4"),
   163  		network.WithProviderSubnetID("6"),
   164  		network.WithProviderVLANID("5002"),
   165  		network.WithVLANTag(100),
   166  	)
   167  	c.Check(addrs, jc.DeepEquals, network.ProviderAddresses{{
   168  		MachineAddress: network.MachineAddress{
   169  			Value: "0.2.3.4",
   170  			Type:  "ipv4",
   171  			Scope: "public",
   172  		},
   173  		SpaceName:        "bar",
   174  		ProviderSpaceID:  "4",
   175  		ProviderSubnetID: "6",
   176  		ProviderVLANID:   "5002",
   177  		VLANTag:          100,
   178  	}, {
   179  		MachineAddress: network.MachineAddress{
   180  			Value: "fc00::1",
   181  			Type:  "ipv6",
   182  			Scope: "local-cloud",
   183  		},
   184  		SpaceName:        "bar",
   185  		ProviderSpaceID:  "4",
   186  		ProviderSubnetID: "6",
   187  		ProviderVLANID:   "5002",
   188  		VLANTag:          100,
   189  	}})
   190  }
   191  
   192  func (s *AddressSuite) TestNewAddressIPv4(c *gc.C) {
   193  	value := "0.1.2.3"
   194  	addr1 := network.NewSpaceAddress(value)
   195  	addr2 := network.NewSpaceAddress(value, network.WithScope(network.ScopeLinkLocal))
   196  	c.Assert(addr1.Scope, gc.Equals, network.ScopePublic) // derived from value
   197  	c.Assert(addr1.Value, gc.Equals, value)
   198  	c.Assert(addr1.Type, gc.Equals, network.IPv4Address)
   199  	c.Assert(addr2.Scope, gc.Equals, network.ScopeLinkLocal)
   200  }
   201  
   202  func (s *AddressSuite) TestNewAddressIPv6(c *gc.C) {
   203  	value := "2001:db8::1"
   204  	addr1 := network.NewSpaceAddress(value)
   205  	addr2 := network.NewSpaceAddress(value, network.WithScope(network.ScopeLinkLocal))
   206  	c.Assert(addr1.Scope, gc.Equals, network.ScopePublic) // derived from value
   207  	c.Assert(addr1.Value, gc.Equals, value)
   208  	c.Assert(addr1.Type, gc.Equals, network.IPv6Address)
   209  	c.Assert(addr2.Scope, gc.Equals, network.ScopeLinkLocal)
   210  }
   211  
   212  func (s *AddressSuite) TestNewAddresses(c *gc.C) {
   213  	testAddresses := []struct {
   214  		values   []string
   215  		addrType network.AddressType
   216  		scope    network.Scope
   217  	}{{
   218  		[]string{"127.0.0.1", "127.0.1.2"},
   219  		network.IPv4Address,
   220  		network.ScopeMachineLocal,
   221  	}, {
   222  		[]string{"::1"},
   223  		network.IPv6Address,
   224  		network.ScopeMachineLocal,
   225  	}, {
   226  		[]string{"192.168.1.1", "192.168.178.255", "10.5.1.1", "172.16.1.1"},
   227  		network.IPv4Address,
   228  		network.ScopeCloudLocal,
   229  	}, {
   230  		[]string{"fc00::1", "fd00::2"},
   231  		network.IPv6Address,
   232  		network.ScopeCloudLocal,
   233  	}, {
   234  		[]string{"8.8.8.8", "8.8.4.4"},
   235  		network.IPv4Address,
   236  		network.ScopePublic,
   237  	}, {
   238  		[]string{"2001:db8::1", "64:ff9b::1", "2002::1"},
   239  		network.IPv6Address,
   240  		network.ScopePublic,
   241  	}, {
   242  		[]string{"169.254.1.23", "169.254.1.1"},
   243  		network.IPv4Address,
   244  		network.ScopeLinkLocal,
   245  	}, {
   246  		[]string{"243.1.5.7", "245.3.1.2"},
   247  		network.IPv4Address,
   248  		network.ScopeFanLocal,
   249  	}, {
   250  		[]string{"ff01::2", "ff01::1"},
   251  		network.IPv6Address,
   252  		network.ScopeLinkLocal,
   253  	}, {
   254  		[]string{"example.com", "example.org"},
   255  		network.HostName,
   256  		network.ScopeUnknown,
   257  	}}
   258  
   259  	for i, test := range testAddresses {
   260  		c.Logf("test %d: %v -> %q", i, test.values, test.scope)
   261  		addresses := network.NewSpaceAddresses(test.values...)
   262  		c.Check(addresses, gc.HasLen, len(test.values))
   263  		for j, addr := range addresses {
   264  			c.Check(addr.Value, gc.Equals, test.values[j])
   265  			c.Check(addr.Type, gc.Equals, test.addrType)
   266  			c.Check(addr.Scope, gc.Equals, test.scope)
   267  		}
   268  	}
   269  }
   270  
   271  func (s *AddressSuite) TestNewScopedAddressHostname(c *gc.C) {
   272  	addr := network.NewSpaceAddress("localhost")
   273  	c.Check(addr.Value, gc.Equals, "localhost")
   274  	c.Check(addr.Type, gc.Equals, network.HostName)
   275  	c.Check(addr.Scope, gc.Equals, network.ScopeUnknown)
   276  	addr = network.NewSpaceAddress("example.com")
   277  	c.Check(addr.Value, gc.Equals, "example.com")
   278  	c.Check(addr.Type, gc.Equals, network.HostName)
   279  	c.Check(addr.Scope, gc.Equals, network.ScopeUnknown)
   280  }
   281  
   282  type selectTest struct {
   283  	about         string
   284  	addresses     network.SpaceAddresses
   285  	expectedIndex int
   286  }
   287  
   288  // expected returns the expected address for the test.
   289  func (t selectTest) expected() (network.SpaceAddress, bool) {
   290  	if t.expectedIndex == -1 {
   291  		return network.SpaceAddress{}, false
   292  	}
   293  	return t.addresses[t.expectedIndex], true
   294  }
   295  
   296  var selectPublicTests = []selectTest{{
   297  	"no addresses gives empty string result",
   298  	[]network.SpaceAddress{},
   299  	-1,
   300  }, {
   301  	"a public IPv4 address is selected",
   302  	[]network.SpaceAddress{
   303  		network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)),
   304  	},
   305  	0,
   306  }, {
   307  	"a public IPv6 address is selected",
   308  	[]network.SpaceAddress{
   309  		network.NewSpaceAddress("2001:db8::1", network.WithScope(network.ScopePublic)),
   310  	},
   311  	0,
   312  }, {
   313  	"first public address is selected",
   314  	[]network.SpaceAddress{
   315  		network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)),
   316  		network.NewSpaceAddress("2001:db8::1", network.WithScope(network.ScopePublic)),
   317  	},
   318  	0,
   319  }, {
   320  	"the first public address is selected when cloud local fallbacks exist",
   321  	[]network.SpaceAddress{
   322  		network.NewSpaceAddress("172.16.1.1", network.WithScope(network.ScopeCloudLocal)),
   323  		network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)),
   324  		network.NewSpaceAddress("fc00:1", network.WithScope(network.ScopeCloudLocal)),
   325  		network.NewSpaceAddress("2001:db8::1", network.WithScope(network.ScopePublic)),
   326  	},
   327  	1,
   328  }, {
   329  	"the cloud local address is selected when a fan-local fallback exists",
   330  	[]network.SpaceAddress{
   331  		network.NewSpaceAddress("243.1.1.1", network.WithScope(network.ScopeFanLocal)),
   332  		network.NewSpaceAddress("172.16.1.1", network.WithScope(network.ScopeCloudLocal)),
   333  	},
   334  	1,
   335  },
   336  	{
   337  		"a machine IPv4 local address is not selected",
   338  		[]network.SpaceAddress{
   339  			network.NewSpaceAddress("127.0.0.1", network.WithScope(network.ScopeMachineLocal)),
   340  		},
   341  		-1,
   342  	}, {
   343  		"a machine IPv6 local address is not selected",
   344  		[]network.SpaceAddress{
   345  			network.NewSpaceAddress("::1", network.WithScope(network.ScopeMachineLocal)),
   346  		},
   347  		-1,
   348  	}, {
   349  		"a link-local IPv4 address is not selected",
   350  		[]network.SpaceAddress{
   351  			network.NewSpaceAddress("169.254.1.1", network.WithScope(network.ScopeLinkLocal)),
   352  		},
   353  		-1,
   354  	}, {
   355  		"a link-local (multicast or not) IPv6 address is not selected",
   356  		[]network.SpaceAddress{
   357  			network.NewSpaceAddress("fe80::1", network.WithScope(network.ScopeLinkLocal)),
   358  			network.NewSpaceAddress("ff01::2", network.WithScope(network.ScopeLinkLocal)),
   359  			network.NewSpaceAddress("ff02::1:1", network.WithScope(network.ScopeLinkLocal)),
   360  		},
   361  		-1,
   362  	}, {
   363  		"a public name is preferred to an unknown or cloud local address",
   364  		[]network.SpaceAddress{
   365  			network.NewSpaceAddress("127.0.0.1"),
   366  			network.NewSpaceAddress("10.0.0.1", network.WithScope(network.ScopeCloudLocal)),
   367  			network.NewSpaceAddress("fc00::1", network.WithScope(network.ScopeCloudLocal)),
   368  			network.NewSpaceAddress("public.invalid.testing", network.WithScope(network.ScopePublic)),
   369  		},
   370  		3,
   371  	}, {
   372  		"first unknown address selected",
   373  		// NOTE(dimitern): Not using NewSpaceAddress() below as it derives the
   374  		// scope internally from the value when given ScopeUnknown.
   375  		[]network.SpaceAddress{
   376  			{
   377  				MachineAddress: network.MachineAddress{
   378  					Value: "10.0.0.1",
   379  					Scope: network.ScopeUnknown,
   380  				},
   381  			},
   382  			{
   383  				MachineAddress: network.MachineAddress{
   384  					Value: "8.8.8.8",
   385  					Scope: network.ScopeUnknown,
   386  				},
   387  			},
   388  		},
   389  		0,
   390  	}, {
   391  		"public IP address is picked when both public IPs and public hostnames exist",
   392  		[]network.SpaceAddress{
   393  			network.NewSpaceAddress("10.0.0.1"),
   394  			network.NewSpaceAddress("example.com", network.WithScope(network.ScopePublic)),
   395  			network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)),
   396  		},
   397  		2,
   398  	}, {
   399  		"hostname is picked over cloud local address",
   400  		[]network.SpaceAddress{
   401  			network.NewSpaceAddress("10.0.0.1"),
   402  			network.NewSpaceAddress("example.com", network.WithScope(network.ScopePublic)),
   403  		},
   404  		1,
   405  	}, {
   406  		"IPv4 preferred over IPv6",
   407  		[]network.SpaceAddress{
   408  			network.NewSpaceAddress("2001:db8::1", network.WithScope(network.ScopePublic)),
   409  			network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)),
   410  		},
   411  		1,
   412  	}}
   413  
   414  func (s *AddressSuite) TestSelectPublicAddress(c *gc.C) {
   415  	for i, t := range selectPublicTests {
   416  		c.Logf("test %d: %s", i, t.about)
   417  		expectAddr, expectOK := t.expected()
   418  		actualAddr, actualOK := t.addresses.OneMatchingScope(network.ScopeMatchPublic)
   419  		c.Check(actualOK, gc.Equals, expectOK)
   420  		c.Check(actualAddr, gc.Equals, expectAddr)
   421  	}
   422  }
   423  
   424  var selectInternalTests = []selectTest{{
   425  	"no addresses gives empty string result",
   426  	[]network.SpaceAddress{},
   427  	-1,
   428  }, {
   429  	"a public IPv4 address is selected",
   430  	[]network.SpaceAddress{
   431  		network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)),
   432  	},
   433  	0,
   434  }, {
   435  	"a public IPv6 address is selected",
   436  	[]network.SpaceAddress{
   437  		network.NewSpaceAddress("2001:db8::1", network.WithScope(network.ScopePublic)),
   438  	},
   439  	0,
   440  }, {
   441  	"a cloud local IPv4 address is selected",
   442  	[]network.SpaceAddress{
   443  		network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)),
   444  		network.NewSpaceAddress("10.0.0.1", network.WithScope(network.ScopeCloudLocal)),
   445  	},
   446  	1,
   447  }, {
   448  	"a cloud local IPv6 address is selected",
   449  	[]network.SpaceAddress{
   450  		network.NewSpaceAddress("fc00::1", network.WithScope(network.ScopeCloudLocal)),
   451  		network.NewSpaceAddress("2001:db8::1", network.WithScope(network.ScopePublic)),
   452  	},
   453  	0,
   454  }, {
   455  	"a machine local or link-local address is not selected",
   456  	[]network.SpaceAddress{
   457  		network.NewSpaceAddress("127.0.0.1", network.WithScope(network.ScopeMachineLocal)),
   458  		network.NewSpaceAddress("::1", network.WithScope(network.ScopeMachineLocal)),
   459  		network.NewSpaceAddress("fe80::1", network.WithScope(network.ScopeLinkLocal)),
   460  	},
   461  	-1,
   462  }, {
   463  	"a cloud local address is preferred to a public address",
   464  	[]network.SpaceAddress{
   465  		network.NewSpaceAddress("2001:db8::1", network.WithScope(network.ScopePublic)),
   466  		network.NewSpaceAddress("fc00::1", network.WithScope(network.ScopeCloudLocal)),
   467  		network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)),
   468  	},
   469  	1,
   470  }, {
   471  	"an IPv6 cloud local address is preferred to a public address if the former appears first",
   472  	[]network.SpaceAddress{
   473  		network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)),
   474  		network.NewSpaceAddress("2001:db8::1", network.WithScope(network.ScopePublic)),
   475  		network.NewSpaceAddress("fc00::1", network.WithScope(network.ScopeCloudLocal)),
   476  	},
   477  	2,
   478  }}
   479  
   480  func (s *AddressSuite) TestSelectInternalAddress(c *gc.C) {
   481  	for i, t := range selectInternalTests {
   482  		c.Logf("test %d: %s", i, t.about)
   483  		expectAddr, expectOK := t.expected()
   484  		actualAddr, actualOK := t.addresses.OneMatchingScope(network.ScopeMatchCloudLocal)
   485  		c.Check(actualOK, gc.Equals, expectOK)
   486  		c.Check(actualAddr, gc.Equals, expectAddr)
   487  	}
   488  }
   489  
   490  var selectInternalMachineTests = []selectTest{{
   491  	"first cloud local IPv4 address is selected",
   492  	[]network.SpaceAddress{
   493  		network.NewSpaceAddress("fc00::1", network.WithScope(network.ScopePublic)),
   494  		network.NewSpaceAddress("2001:db8::1", network.WithScope(network.ScopePublic)),
   495  		network.NewSpaceAddress("10.0.0.1", network.WithScope(network.ScopeCloudLocal)),
   496  		network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)),
   497  	},
   498  	2,
   499  }, {
   500  	"first cloud local address is selected",
   501  	[]network.SpaceAddress{
   502  		network.NewSpaceAddress("fc00::1", network.WithScope(network.ScopeCloudLocal)),
   503  		network.NewSpaceAddress("2001:db8::1", network.WithScope(network.ScopePublic)),
   504  		network.NewSpaceAddress("8.8.8.8", network.WithScope(network.ScopePublic)),
   505  	},
   506  	0,
   507  }, {
   508  	"first cloud local hostname is selected",
   509  	[]network.SpaceAddress{
   510  		network.NewSpaceAddress("example.com", network.WithScope(network.ScopePublic)),
   511  		network.NewSpaceAddress("cloud1.internal", network.WithScope(network.ScopeCloudLocal)),
   512  		network.NewSpaceAddress("cloud2.internal", network.WithScope(network.ScopeCloudLocal)),
   513  		network.NewSpaceAddress("example.org", network.WithScope(network.ScopePublic)),
   514  	},
   515  	1,
   516  }, {
   517  	"first machine local address is selected",
   518  	[]network.SpaceAddress{
   519  		network.NewSpaceAddress("127.0.0.1", network.WithScope(network.ScopeMachineLocal)),
   520  		network.NewSpaceAddress("::1", network.WithScope(network.ScopeMachineLocal)),
   521  	},
   522  	0,
   523  }, {
   524  	"first machine local IPv4 address is selected even with public/cloud hostnames",
   525  	[]network.SpaceAddress{
   526  		network.NewSpaceAddress("public.example.com", network.WithScope(network.ScopePublic)),
   527  		network.NewSpaceAddress("::1", network.WithScope(network.ScopeMachineLocal)),
   528  		network.NewSpaceAddress("unknown.example.com"),
   529  		network.NewSpaceAddress("cloud.internal", network.WithScope(network.ScopeCloudLocal)),
   530  		network.NewSpaceAddress("127.0.0.1", network.WithScope(network.ScopeMachineLocal)),
   531  		network.NewSpaceAddress("fe80::1", network.WithScope(network.ScopeLinkLocal)),
   532  		network.NewSpaceAddress("127.0.0.2", network.WithScope(network.ScopeMachineLocal)),
   533  	},
   534  	4,
   535  }, {
   536  	"first machine local non-IPv4 address is selected even with public/cloud hostnames",
   537  	[]network.SpaceAddress{
   538  		network.NewSpaceAddress("public.example.com", network.WithScope(network.ScopePublic)),
   539  		network.NewSpaceAddress("::1", network.WithScope(network.ScopeMachineLocal)),
   540  		network.NewSpaceAddress("unknown.example.com"),
   541  		network.NewSpaceAddress("cloud.internal", network.WithScope(network.ScopeCloudLocal)),
   542  		network.NewSpaceAddress("fe80::1", network.WithScope(network.ScopeLinkLocal)),
   543  	},
   544  	1,
   545  }, {
   546  	"cloud local IPv4 is selected even with other machine/cloud addresses",
   547  	[]network.SpaceAddress{
   548  		network.NewSpaceAddress("169.254.1.1", network.WithScope(network.ScopeLinkLocal)),
   549  		network.NewSpaceAddress("cloud-unknown.internal"),
   550  		network.NewSpaceAddress("cloud-local.internal", network.WithScope(network.ScopeCloudLocal)),
   551  		network.NewSpaceAddress("fc00::1", network.WithScope(network.ScopeCloudLocal)),
   552  		network.NewSpaceAddress("127.0.0.1", network.WithScope(network.ScopeMachineLocal)),
   553  		network.NewSpaceAddress("127.0.0.2", network.WithScope(network.ScopeMachineLocal)),
   554  	},
   555  	4,
   556  }, {
   557  	"first cloud local hostname is selected even with other machine/cloud addresses",
   558  	[]network.SpaceAddress{
   559  		network.NewSpaceAddress("169.254.1.1", network.WithScope(network.ScopeLinkLocal)),
   560  		network.NewSpaceAddress("cloud-unknown.internal"),
   561  		network.NewSpaceAddress("cloud-local.internal", network.WithScope(network.ScopeCloudLocal)),
   562  		network.NewSpaceAddress("fc00::1", network.WithScope(network.ScopeCloudLocal)),
   563  	},
   564  	2,
   565  }}
   566  
   567  func (s *AddressSuite) TestSelectInternalMachineAddress(c *gc.C) {
   568  	for i, t := range selectInternalMachineTests {
   569  		c.Logf("test %d: %s", i, t.about)
   570  		expectAddr, expectOK := t.expected()
   571  		actualAddr, actualOK := t.addresses.OneMatchingScope(network.ScopeMatchMachineOrCloudLocal)
   572  		c.Check(actualOK, gc.Equals, expectOK)
   573  		c.Check(actualAddr, gc.Equals, expectAddr)
   574  	}
   575  }
   576  
   577  type selectInternalAddressesTest struct {
   578  	about     string
   579  	addresses network.SpaceAddresses
   580  	matcher   network.ScopeMatchFunc
   581  	expected  network.SpaceAddresses
   582  }
   583  
   584  var selectInternalAddressesTests = []selectInternalAddressesTest{
   585  	{
   586  		about: "machine/cloud-local addresses are selected when machineLocal is true",
   587  		addresses: []network.SpaceAddress{
   588  			network.NewSpaceAddress("127.0.0.1", network.WithScope(network.ScopeMachineLocal)),
   589  			network.NewSpaceAddress("10.0.0.9", network.WithScope(network.ScopeCloudLocal)),
   590  			network.NewSpaceAddress("fc00::1", network.WithScope(network.ScopePublic)),
   591  		},
   592  		matcher: network.ScopeMatchMachineOrCloudLocal,
   593  		expected: []network.SpaceAddress{
   594  			network.NewSpaceAddress("127.0.0.1", network.WithScope(network.ScopeMachineLocal)),
   595  			network.NewSpaceAddress("10.0.0.9", network.WithScope(network.ScopeCloudLocal)),
   596  		},
   597  	},
   598  	{
   599  		about: "cloud-local addresses are selected when machineLocal is false",
   600  		addresses: []network.SpaceAddress{
   601  			network.NewSpaceAddress("169.254.1.1", network.WithScope(network.ScopeLinkLocal)),
   602  			network.NewSpaceAddress("127.0.0.1", network.WithScope(network.ScopeMachineLocal)),
   603  			network.NewSpaceAddress("cloud-local.internal", network.WithScope(network.ScopeCloudLocal)),
   604  			network.NewSpaceAddress("cloud-local2.internal", network.WithScope(network.ScopeCloudLocal)),
   605  			network.NewSpaceAddress("fc00::1", network.WithScope(network.ScopePublic)),
   606  		},
   607  		matcher: network.ScopeMatchCloudLocal,
   608  		expected: []network.SpaceAddress{
   609  			network.NewSpaceAddress("cloud-local.internal", network.WithScope(network.ScopeCloudLocal)),
   610  			network.NewSpaceAddress("cloud-local2.internal", network.WithScope(network.ScopeCloudLocal)),
   611  		},
   612  	},
   613  	{
   614  		about: "nil is returned when no cloud-local addresses are found",
   615  		addresses: []network.SpaceAddress{
   616  			network.NewSpaceAddress("169.254.1.1", network.WithScope(network.ScopeLinkLocal)),
   617  			network.NewSpaceAddress("127.0.0.1", network.WithScope(network.ScopeMachineLocal)),
   618  		},
   619  		matcher:  network.ScopeMatchCloudLocal,
   620  		expected: nil,
   621  	},
   622  }
   623  
   624  func (s *AddressSuite) TestSelectInternalAddresses(c *gc.C) {
   625  	for i, t := range selectInternalAddressesTests {
   626  		c.Logf("test %d: %s", i, t.about)
   627  		actualAddr := t.addresses.AllMatchingScope(t.matcher)
   628  		c.Check(actualAddr, gc.DeepEquals, t.expected)
   629  	}
   630  }
   631  
   632  // stringer wraps Stringer and GoStringer for convenience.
   633  type stringer interface {
   634  	fmt.Stringer
   635  	fmt.GoStringer
   636  }
   637  
   638  var stringTests = []struct {
   639  	addr stringer
   640  	str  string
   641  }{{
   642  	addr: network.MachineAddress{
   643  		Type:  network.IPv4Address,
   644  		Value: "127.0.0.1",
   645  	},
   646  	str: "127.0.0.1",
   647  }, {
   648  	addr: network.ProviderAddress{
   649  		MachineAddress: network.MachineAddress{
   650  			Type:  network.IPv4Address,
   651  			Value: "127.0.0.1",
   652  		},
   653  		SpaceName: "storage-data",
   654  	},
   655  	str: "127.0.0.1@storage-data",
   656  }, {
   657  	addr: network.MachineAddress{
   658  		Type:  network.IPv6Address,
   659  		Value: "2001:db8::1",
   660  		Scope: network.ScopePublic,
   661  	},
   662  	str: "public:2001:db8::1",
   663  }, {
   664  	addr: network.MachineAddress{
   665  		Type:  network.HostName,
   666  		Value: "foo.com",
   667  	},
   668  	str: "foo.com",
   669  }, {
   670  	addr: network.MachineAddress{
   671  		Type:  network.HostName,
   672  		Value: "foo.com",
   673  		Scope: network.ScopeUnknown,
   674  	},
   675  	str: "foo.com",
   676  }, {
   677  	addr: network.ProviderAddress{
   678  		MachineAddress: network.MachineAddress{
   679  			Type:  network.HostName,
   680  			Value: "foo.com",
   681  			Scope: network.ScopePublic,
   682  		},
   683  		ProviderSpaceID: network.Id("3"),
   684  	},
   685  	str: "public:foo.com@(id:3)",
   686  }, {
   687  	addr: network.ProviderAddress{
   688  		MachineAddress: network.MachineAddress{
   689  			Type:  network.HostName,
   690  			Value: "foo.com",
   691  			Scope: network.ScopePublic,
   692  		},
   693  		SpaceName: "default",
   694  	},
   695  	str: "public:foo.com@default",
   696  }, {
   697  	addr: network.ProviderAddress{
   698  		MachineAddress: network.MachineAddress{
   699  			Type:  network.HostName,
   700  			Value: "foo.com",
   701  			Scope: network.ScopePublic,
   702  		},
   703  		SpaceName:       "badlands",
   704  		ProviderSpaceID: network.Id("3"),
   705  	},
   706  	str: "public:foo.com@badlands(id:3)",
   707  }, {
   708  	addr: network.ProviderAddress{
   709  		MachineAddress: network.MachineAddress{
   710  			Type:  network.HostName,
   711  			Value: "foo.com",
   712  			Scope: network.ScopePublic,
   713  		},
   714  		SpaceName:       "badlands",
   715  		ProviderSpaceID: network.Id("3"),
   716  		ProviderID:      "523",
   717  	},
   718  	str: "public:foo.com@badlands(id:3)",
   719  }, {
   720  	addr: network.ProviderAddress{
   721  		MachineAddress: network.MachineAddress{
   722  			Type:  network.HostName,
   723  			Value: "foo.com",
   724  			Scope: network.ScopePublic,
   725  		},
   726  		SpaceName:        "badlands",
   727  		ProviderSpaceID:  network.Id("3"),
   728  		ProviderSubnetID: "5",
   729  	},
   730  	str: "public:foo.com@badlands(id:3)",
   731  }, {
   732  	addr: network.ProviderAddress{
   733  		MachineAddress: network.MachineAddress{
   734  			Type:  network.HostName,
   735  			Value: "foo.com",
   736  			Scope: network.ScopePublic,
   737  		},
   738  		SpaceName:       "badlands",
   739  		ProviderSpaceID: network.Id("3"),
   740  		ProviderVLANID:  "5001",
   741  	},
   742  	str: "public:foo.com@badlands(id:3)",
   743  }, {
   744  	addr: network.ProviderAddress{
   745  		MachineAddress: network.MachineAddress{
   746  			Type:  network.HostName,
   747  			Value: "foo.com",
   748  			Scope: network.ScopePublic,
   749  		},
   750  		SpaceName:       "badlands",
   751  		ProviderSpaceID: network.Id("3"),
   752  		VLANTag:         50,
   753  	},
   754  	str: "public:foo.com@badlands(id:3)",
   755  }}
   756  
   757  func (s *AddressSuite) TestString(c *gc.C) {
   758  	for i, test := range stringTests {
   759  		c.Logf("test %d: %#v", i, test.addr)
   760  		c.Check(test.addr.String(), gc.Equals, test.str)
   761  		c.Check(test.addr.GoString(), gc.Equals, test.str)
   762  	}
   763  }
   764  
   765  func (*AddressSuite) TestSortAddresses(c *gc.C) {
   766  	addrs := network.NewSpaceAddresses(
   767  		"127.0.0.1",
   768  		"::1",
   769  		"fc00::1",
   770  		"169.254.1.2",
   771  		"localhost",
   772  		"243.5.1.2",
   773  		"2001:db8::1",
   774  		"fe80::2",
   775  		"7.8.8.8",
   776  		"172.16.0.2",
   777  		"example.com",
   778  		"8.8.8.8",
   779  	)
   780  
   781  	// Public and local-cloud secondary addresses.
   782  	addrs = append(addrs, network.NewSpaceAddress("6.8.8.8", network.WithSecondary(true)))
   783  	addrs = append(addrs, network.NewSpaceAddress("172.16.0.1", network.WithSecondary(true)))
   784  
   785  	sort.Sort(addrs)
   786  	c.Assert(addrs.Values(), jc.DeepEquals, []string{
   787  		// Public IPv4 addresses on top.
   788  		"7.8.8.8",
   789  		"8.8.8.8",
   790  		// After that public IPv6 addresses.
   791  		"2001:db8::1",
   792  		// Then hostname.
   793  		"example.com",
   794  		// Secondary public address follows the others.
   795  		"6.8.8.8",
   796  		// With localhost last.
   797  		"localhost",
   798  		// Then IPv4 cloud-local addresses.
   799  		"172.16.0.2",
   800  		// Then IPv6 cloud-local addresses.
   801  		"fc00::1",
   802  		// Then secondary cloud-local addresses.
   803  		"172.16.0.1",
   804  		// Then fan-local addresses.
   805  		"243.5.1.2",
   806  		// Then machine-local IPv4 addresses.
   807  		"127.0.0.1",
   808  		// Then machine-local IPv6 addresses.
   809  		"::1",
   810  		// Then link-local IPv4 addresses.
   811  		"169.254.1.2",
   812  		// Finally, link-local IPv6 addresses.
   813  		"fe80::2",
   814  	})
   815  }
   816  
   817  func (*AddressSuite) TestExactScopeMatch(c *gc.C) {
   818  	var addr network.Address
   819  
   820  	addr = network.NewMachineAddress("10.0.0.2", network.WithScope(network.ScopeCloudLocal))
   821  	match := network.ExactScopeMatch(addr, network.ScopeCloudLocal)
   822  	c.Assert(match, jc.IsTrue)
   823  	match = network.ExactScopeMatch(addr, network.ScopePublic)
   824  	c.Assert(match, jc.IsFalse)
   825  
   826  	addr = network.NewMachineAddress("8.8.8.8", network.WithScope(network.ScopePublic)).AsProviderAddress()
   827  	match = network.ExactScopeMatch(addr, network.ScopeCloudLocal)
   828  	c.Assert(match, jc.IsFalse)
   829  	match = network.ExactScopeMatch(addr, network.ScopePublic)
   830  	c.Assert(match, jc.IsTrue)
   831  }
   832  
   833  func (s *AddressSuite) TestSelectAddressesBySpaceNamesFiltered(c *gc.C) {
   834  	sp := network.SpaceInfo{
   835  		ID:         "666",
   836  		Name:       "thaSpace",
   837  		ProviderId: "",
   838  		Subnets:    nil,
   839  	}
   840  
   841  	// Only the first address has a space.
   842  	addr := network.NewSpaceAddress("192.168.5.5")
   843  	addr.SpaceID = sp.ID
   844  	addrs := network.SpaceAddresses{
   845  		addr,
   846  		network.NewSpaceAddress("127.0.0.1"),
   847  	}
   848  
   849  	filtered, ok := addrs.InSpaces(sp)
   850  	c.Check(ok, jc.IsTrue)
   851  	c.Check(filtered, jc.DeepEquals, network.SpaceAddresses{addr})
   852  }
   853  
   854  func (s *AddressSuite) TestSelectAddressesBySpaceNoSpaceFalse(c *gc.C) {
   855  	addrs := network.SpaceAddresses{network.NewSpaceAddress("127.0.0.1")}
   856  	filtered, ok := addrs.InSpaces()
   857  	c.Check(ok, jc.IsFalse)
   858  	c.Check(filtered, jc.DeepEquals, addrs)
   859  }
   860  
   861  func (s *AddressSuite) TestSelectAddressesBySpaceNoneFound(c *gc.C) {
   862  	sp := network.SpaceInfo{
   863  		ID:         "666",
   864  		Name:       "noneSpace",
   865  		ProviderId: "",
   866  		Subnets:    nil,
   867  	}
   868  
   869  	addrs := network.SpaceAddresses{network.NewSpaceAddress("127.0.0.1")}
   870  	filtered, ok := addrs.InSpaces(sp)
   871  	c.Check(ok, jc.IsFalse)
   872  	c.Check(filtered, jc.DeepEquals, addrs)
   873  }
   874  
   875  type stubLookup struct{}
   876  
   877  var _ network.SpaceLookup = stubLookup{}
   878  
   879  func (s stubLookup) AllSpaceInfos() (network.SpaceInfos, error) {
   880  	return network.SpaceInfos{
   881  		{ID: "1", Name: "space-one", ProviderId: "p1"},
   882  		{ID: "2", Name: "space-two"},
   883  		{
   884  			ID:   "6",
   885  			Name: "space-six",
   886  			Subnets: network.SubnetInfos{
   887  				{
   888  					ID:         "666",
   889  					CIDR:       "10.0.0.0/24",
   890  					ProviderId: "61",
   891  				},
   892  			},
   893  		},
   894  	}, nil
   895  }
   896  
   897  func (s *AddressSuite) TestProviderAddressesToSpaceAddressesByName(c *gc.C) {
   898  	// Check success.
   899  	addrs := network.ProviderAddresses{
   900  		network.NewMachineAddress("1.2.3.4").AsProviderAddress(network.WithSpaceName("space-one")),
   901  		network.NewMachineAddress("2.3.4.5").AsProviderAddress(network.WithSpaceName("space-two")),
   902  		network.NewMachineAddress("3.4.5.6").AsProviderAddress(),
   903  	}
   904  
   905  	exp := network.NewSpaceAddresses("1.2.3.4", "2.3.4.5", "3.4.5.6")
   906  	exp[0].SpaceID = "1"
   907  	exp[1].SpaceID = "2"
   908  
   909  	res, err := addrs.ToSpaceAddresses(stubLookup{})
   910  	c.Assert(err, jc.ErrorIsNil)
   911  	c.Check(res, jc.SameContents, exp)
   912  
   913  	// Add an address in a space that the lookup will not resolve.
   914  	addrs = append(addrs, network.NewMachineAddress("4.5.6.7").AsProviderAddress(network.WithSpaceName("space-denied")))
   915  	_, err = addrs.ToSpaceAddresses(stubLookup{})
   916  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   917  }
   918  
   919  func (s *AddressSuite) TestProviderAddressesToSpaceAddressesBySubnet(c *gc.C) {
   920  	// Check success.
   921  	addrs := network.ProviderAddresses{
   922  		network.NewMachineAddress(
   923  			"10.0.0.6",
   924  			network.WithCIDR("10.0.0.0/24"),
   925  		).AsProviderAddress(network.WithProviderSubnetID("61")),
   926  	}
   927  
   928  	res, err := addrs.ToSpaceAddresses(stubLookup{})
   929  	c.Assert(err, jc.ErrorIsNil)
   930  	c.Assert(res, gc.HasLen, 1)
   931  	c.Check(res[0].SpaceID, gc.Equals, "6")
   932  }
   933  
   934  func (s *AddressSuite) TestSpaceAddressesToProviderAddresses(c *gc.C) {
   935  	// Check success.
   936  	addrs := network.NewSpaceAddresses("1.2.3.4", "2.3.4.5", "3.4.5.6")
   937  	addrs[0].SpaceID = "1"
   938  	addrs[1].SpaceID = "2"
   939  
   940  	exp := network.ProviderAddresses{
   941  		network.NewMachineAddress("1.2.3.4").AsProviderAddress(network.WithSpaceName("space-one")),
   942  		network.NewMachineAddress("2.3.4.5").AsProviderAddress(network.WithSpaceName("space-two")),
   943  		network.NewMachineAddress("3.4.5.6").AsProviderAddress(),
   944  	}
   945  	// Only the first address in the lookup has a provider ID.
   946  	exp[0].ProviderSpaceID = "p1"
   947  
   948  	res, err := addrs.ToProviderAddresses(stubLookup{})
   949  	c.Assert(err, jc.ErrorIsNil)
   950  	c.Check(res, jc.SameContents, exp)
   951  
   952  	// Add an address in a space that the lookup will not resolve.
   953  	addrs = append(addrs, network.NewSpaceAddress("4.5.6.7"))
   954  	addrs[3].SpaceID = "3"
   955  	_, err = addrs.ToProviderAddresses(stubLookup{})
   956  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   957  }
   958  
   959  func (s *AddressSuite) TestSpaceAddressesValues(c *gc.C) {
   960  	values := []string{"1.2.3.4", "2.3.4.5", "3.4.5.6"}
   961  	addrs := network.NewSpaceAddresses(values...)
   962  	c.Check(addrs.Values(), gc.DeepEquals, values)
   963  }
   964  
   965  func (s *AddressSuite) TestAddressValueForCIDR(c *gc.C) {
   966  	_, err := network.NewMachineAddress("172.31.37.53").ValueWithMask()
   967  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   968  
   969  	_, err = network.NewMachineAddress("", network.WithCIDR("172.31.37.0/20")).ValueWithMask()
   970  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   971  
   972  	val, err := network.NewMachineAddress("172.31.37.53", network.WithCIDR("172.31.37.0/20")).ValueWithMask()
   973  	c.Assert(err, jc.ErrorIsNil)
   974  	c.Check(val, gc.Equals, "172.31.37.53/20")
   975  }
   976  
   977  func (s *AddressSuite) TestCIDRAddressType(c *gc.C) {
   978  	tests := []struct {
   979  		descr  string
   980  		CIDR   string
   981  		exp    network.AddressType
   982  		expErr string
   983  	}{
   984  		{
   985  			descr: "IPV4 CIDR",
   986  			CIDR:  "10.0.0.0/24",
   987  			exp:   network.IPv4Address,
   988  		},
   989  		{
   990  			descr: "IPV6 CIDR",
   991  			CIDR:  "2002::1234:abcd:ffff:c0a8:101/64",
   992  			exp:   network.IPv6Address,
   993  		},
   994  		{
   995  			descr: "IPV6 with 4in6 prefix",
   996  			CIDR:  "0:0:0:0:0:ffff:c0a8:2a00/120",
   997  			// The Go stdlib interprets this as an IPV4
   998  			exp: network.IPv4Address,
   999  		},
  1000  		{
  1001  			descr:  "bogus CIDR",
  1002  			CIDR:   "catastrophe",
  1003  			expErr: ".*invalid CIDR address.*",
  1004  		},
  1005  	}
  1006  
  1007  	for i, t := range tests {
  1008  		c.Logf("test %d: %s", i, t.descr)
  1009  		got, err := network.CIDRAddressType(t.CIDR)
  1010  		if t.expErr != "" {
  1011  			c.Check(got, gc.Equals, network.AddressType(""))
  1012  			c.Check(err, gc.ErrorMatches, t.expErr)
  1013  		} else {
  1014  			c.Check(err, jc.ErrorIsNil)
  1015  			c.Check(got, gc.Equals, t.exp)
  1016  		}
  1017  	}
  1018  }
  1019  
  1020  func (s *AddressSuite) TestNoAddressError(c *gc.C) {
  1021  	err := network.NoAddressError("fake")
  1022  	c.Assert(err, gc.ErrorMatches, `no fake address\(es\)`)
  1023  	c.Assert(network.IsNoAddressError(err), jc.IsTrue)
  1024  	c.Assert(network.IsNoAddressError(errors.New("address found")), jc.IsFalse)
  1025  }
  1026  
  1027  func (s *AddressSuite) TestNetworkCIDRFromIPAndMask(c *gc.C) {
  1028  	specs := []struct {
  1029  		descr   string
  1030  		ip      net.IP
  1031  		mask    net.IPMask
  1032  		expCIDR string
  1033  	}{
  1034  		{
  1035  			descr:   "nil ip",
  1036  			mask:    net.IPv4Mask(0, 0, 0, 255),
  1037  			expCIDR: "",
  1038  		},
  1039  		{
  1040  			descr:   "nil mask",
  1041  			ip:      net.ParseIP("10.1.2.42"),
  1042  			expCIDR: "",
  1043  		},
  1044  		{
  1045  			descr:   "network IP",
  1046  			ip:      net.ParseIP("10.1.0.0"),
  1047  			mask:    net.IPv4Mask(255, 255, 0, 0),
  1048  			expCIDR: "10.1.0.0/16",
  1049  		},
  1050  		{
  1051  			descr:   "host IP",
  1052  			ip:      net.ParseIP("10.1.2.42"),
  1053  			mask:    net.IPv4Mask(255, 255, 255, 0),
  1054  			expCIDR: "10.1.2.0/24",
  1055  		},
  1056  	}
  1057  
  1058  	for i, spec := range specs {
  1059  		c.Logf("%d: %s", i, spec.descr)
  1060  		gotCIDR := network.NetworkCIDRFromIPAndMask(spec.ip, spec.mask)
  1061  		c.Assert(gotCIDR, gc.Equals, spec.expCIDR)
  1062  	}
  1063  }
  1064  
  1065  func (s *AddressSuite) TestIsValidAddressConfigTypeWithValidValues(c *gc.C) {
  1066  	validTypes := []network.AddressConfigType{
  1067  		network.ConfigLoopback,
  1068  		network.ConfigStatic,
  1069  		network.ConfigDHCP,
  1070  		network.ConfigManual,
  1071  	}
  1072  
  1073  	for _, value := range validTypes {
  1074  		result := network.IsValidAddressConfigType(string(value))
  1075  		c.Check(result, jc.IsTrue)
  1076  	}
  1077  }
  1078  
  1079  func (s *AddressSuite) TestIsValidAddressConfigTypeWithInvalidValues(c *gc.C) {
  1080  	result := network.IsValidAddressConfigType("")
  1081  	c.Check(result, jc.IsFalse)
  1082  
  1083  	result = network.IsValidAddressConfigType("anything")
  1084  	c.Check(result, jc.IsFalse)
  1085  
  1086  	result = network.IsValidAddressConfigType(" ")
  1087  	c.Check(result, jc.IsFalse)
  1088  }
  1089  
  1090  // spaceAddressCandidate implements the SpaceAddressCandidate
  1091  // interface from the core/network package.
  1092  type spaceAddressCandidate struct {
  1093  	value        string
  1094  	configMethod network.AddressConfigType
  1095  	subnetCIDR   string
  1096  	isSecondary  bool
  1097  }
  1098  
  1099  func (s spaceAddressCandidate) Value() string {
  1100  	return s.value
  1101  }
  1102  
  1103  func (s spaceAddressCandidate) ConfigMethod() network.AddressConfigType {
  1104  	return s.configMethod
  1105  }
  1106  
  1107  func (s spaceAddressCandidate) SubnetCIDR() string {
  1108  	return s.subnetCIDR
  1109  }
  1110  
  1111  func (s spaceAddressCandidate) IsSecondary() bool {
  1112  	return s.isSecondary
  1113  }
  1114  
  1115  func (s *AddressSuite) TestConvertToSpaceAddresses(c *gc.C) {
  1116  	subs := network.SubnetInfos{
  1117  		{ID: "1", CIDR: "192.168.0.0/24", SpaceID: "666"},
  1118  		{ID: "2", CIDR: "252.80.0.0/12", SpaceID: "999"},
  1119  		{ID: "3", CIDR: "10.0.0.10/32", SpaceID: "333"},
  1120  	}
  1121  
  1122  	candidates := []network.SpaceAddressCandidate{
  1123  		spaceAddressCandidate{
  1124  			value:        "252.80.0.100",
  1125  			configMethod: network.ConfigStatic,
  1126  			subnetCIDR:   "252.80.0.0/12",
  1127  			isSecondary:  true,
  1128  		},
  1129  		spaceAddressCandidate{
  1130  			value:        "192.168.0.66",
  1131  			configMethod: network.ConfigDHCP,
  1132  			subnetCIDR:   "192.168.0.0/24",
  1133  		},
  1134  		// This simulates an address added to the loopback device,
  1135  		// such as done by BGP.
  1136  		spaceAddressCandidate{
  1137  			value:        "10.0.0.10",
  1138  			configMethod: network.ConfigLoopback,
  1139  			subnetCIDR:   "10.0.0.10/32",
  1140  		},
  1141  	}
  1142  
  1143  	addrs := make(network.SpaceAddresses, len(candidates))
  1144  	for i, ca := range candidates {
  1145  		var err error
  1146  		addrs[i], err = network.ConvertToSpaceAddress(ca, subs)
  1147  		c.Assert(err, jc.ErrorIsNil)
  1148  	}
  1149  
  1150  	sort.Sort(addrs)
  1151  	c.Check(addrs, gc.DeepEquals, network.SpaceAddresses{
  1152  		{
  1153  			MachineAddress: network.MachineAddress{
  1154  				Value:      "10.0.0.10",
  1155  				Type:       network.IPv4Address,
  1156  				Scope:      network.ScopeCloudLocal,
  1157  				ConfigType: network.ConfigLoopback,
  1158  				CIDR:       "10.0.0.10/32",
  1159  			},
  1160  			SpaceID: "333",
  1161  		},
  1162  		{
  1163  			MachineAddress: network.MachineAddress{
  1164  				Value:      "192.168.0.66",
  1165  				Type:       network.IPv4Address,
  1166  				Scope:      network.ScopeCloudLocal,
  1167  				ConfigType: network.ConfigDHCP,
  1168  				CIDR:       "192.168.0.0/24",
  1169  			},
  1170  			SpaceID: "666",
  1171  		},
  1172  		{
  1173  			MachineAddress: network.MachineAddress{
  1174  				Value:       "252.80.0.100",
  1175  				Type:        network.IPv4Address,
  1176  				Scope:       network.ScopeFanLocal,
  1177  				ConfigType:  network.ConfigStatic,
  1178  				CIDR:        "252.80.0.0/12",
  1179  				IsSecondary: true,
  1180  			},
  1181  			SpaceID: "999",
  1182  		},
  1183  	})
  1184  }
  1185  
  1186  type testFindSubnetAddr struct {
  1187  	val string
  1188  }
  1189  
  1190  func (a testFindSubnetAddr) Network() string {
  1191  	return "ip+net"
  1192  }
  1193  
  1194  func (a testFindSubnetAddr) String() string {
  1195  	return a.val
  1196  }
  1197  
  1198  func testAddresses(c *gc.C, networks ...string) ([]net.Addr, error) {
  1199  	addrs := make([]net.Addr, 0)
  1200  	for _, n := range networks {
  1201  		_, _, err := net.ParseCIDR(n)
  1202  		if err != nil {
  1203  			return nil, err
  1204  		}
  1205  		c.Assert(err, gc.IsNil)
  1206  		addrs = append(addrs, testFindSubnetAddr{n})
  1207  	}
  1208  	return addrs, nil
  1209  }
  1210  
  1211  func (s *AddressSuite) TestIsLocalAddress(c *gc.C) {
  1212  
  1213  	tests := []struct {
  1214  		descr            string
  1215  		ip               net.IP
  1216  		ifaceAddrsResult []string
  1217  		ifaceAddrsErr    error
  1218  		expected         bool
  1219  		expectedErr      string
  1220  	}{
  1221  		{
  1222  			descr:         "error returned from net.InterfaceAddrs()",
  1223  			ifaceAddrsErr: errors.New("interface addrs - some error"),
  1224  			expectedErr:   "interface addrs - some error",
  1225  			expected:      false,
  1226  		},
  1227  		{
  1228  			descr:            "IPv4 is local",
  1229  			ifaceAddrsResult: []string{"192.168.0.0/16"},
  1230  			ip:               net.IPv4(192, 168, 0, 0),
  1231  			expected:         true,
  1232  		},
  1233  		{
  1234  			descr:            "IPv4 is not local",
  1235  			ifaceAddrsResult: []string{"192.168.0.0/16"},
  1236  			ip:               net.IPv4(8, 8, 8, 8),
  1237  			expected:         false,
  1238  		},
  1239  		{
  1240  			descr:            "IPv6 is local",
  1241  			ifaceAddrsResult: []string{"fc00::/7"},
  1242  			ip:               net.ParseIP("fc00::0"),
  1243  			expected:         true,
  1244  		},
  1245  		{
  1246  			descr:            "IPv6 is not local",
  1247  			ifaceAddrsResult: []string{"fc00::/7"},
  1248  			ip:               net.ParseIP("2606::1"),
  1249  			expected:         false,
  1250  		},
  1251  	}
  1252  
  1253  	for i, tt := range tests {
  1254  		c.Logf("Test %d: %s", i, tt.descr)
  1255  
  1256  		s.PatchValue(&network.InterfaceAddrs, func() ([]net.Addr, error) {
  1257  			if tt.ifaceAddrsErr != nil {
  1258  				return []net.Addr{}, tt.ifaceAddrsErr
  1259  			}
  1260  			return testAddresses(c, tt.ifaceAddrsResult...)
  1261  		})
  1262  		isLocal, err := network.IsLocalAddress(tt.ip)
  1263  		if err != nil {
  1264  			c.Check(err, gc.ErrorMatches, tt.expectedErr)
  1265  			// when err is returned, isLocal is false
  1266  			c.Check(isLocal, gc.Equals, false)
  1267  			continue
  1268  		}
  1269  		c.Check(err, jc.ErrorIsNil)
  1270  		c.Check("", gc.Equals, tt.expectedErr)
  1271  
  1272  		c.Check(isLocal, gc.Equals, tt.expected)
  1273  	}
  1274  }