diff --git a/go/daemon/network/errors.go b/go/daemon/network/errors.go index 7d45cbc..3327735 100644 --- a/go/daemon/network/errors.go +++ b/go/daemon/network/errors.go @@ -9,6 +9,7 @@ const ( errCodeInitializing = daecommon.ErrorCodeRangeNetwork + iota errCodeInvalidConfig errCodeHostNotFound + errCodeIPInUse ) var ( @@ -25,4 +26,8 @@ var ( // ErrHostNotFound is returned when performing an operation which expected a // host to exist in the network, but that host wasn't found. ErrHostNotFound = jsonrpc2.NewError(errCodeHostNotFound, "Host not found") + + // ErrIPInUse is returned when performing an operation which was provided an + // IP already in use by another host in the network. + ErrIPInUse = jsonrpc2.NewError(errCodeIPInUse, "IP in use") ) diff --git a/go/daemon/network/network.go b/go/daemon/network/network.go index ff0aa1a..b574d2f 100644 --- a/go/daemon/network/network.go +++ b/go/daemon/network/network.go @@ -81,6 +81,10 @@ type RPC interface { // CreateHost creates a bootstrap for a new host with the given name and IP // address. + // + // Errors: + // - ErrIPInUse - if IP field of CreateHostOpts is given, and already in use + // by another host in the network. CreateHost( context.Context, nebula.HostName, CreateHostOpts, ) ( @@ -889,8 +893,13 @@ func (n *network) CreateHost( "choosing available IP: %w", err, ) } + } else { + for _, host := range currBootstrap.Hosts { + if host.IP() == ip { + return JoiningBootstrap{}, ErrIPInUse + } + } } - // TODO if the ip is given, check that it's not already in use. caSigningPrivateKey, err := daecommon.GetNebulaCASigningPrivateKey( ctx, n.secretsStore, diff --git a/go/daemon/network/network_it_test.go b/go/daemon/network/network_it_test.go index 5b0c595..6123436 100644 --- a/go/daemon/network/network_it_test.go +++ b/go/daemon/network/network_it_test.go @@ -98,6 +98,26 @@ func TestNetwork_GetBootstrap(t *testing.T) { ) } +func TestNetwork_CreateHost(t *testing.T) { + t.Parallel() + + // Normal functionality of this method is tested as part of + // `integrationHarness.joinNetwork`. This tests various extra behavior. + + t.Run("ErrIPInUse", func(t *testing.T) { + var ( + h = newIntegrationHarness(t) + network = h.createNetwork(t, "primus", nil) + hostName = nebula.HostName("secondus") + ) + + _, err := network.CreateHost(h.ctx, hostName, CreateHostOpts{ + IP: network.getBootstrap(t).ThisHost().IP(), + }) + assert.ErrorIs(t, err, ErrIPInUse) + }) +} + func TestNetwork_SetConfig(t *testing.T) { t.Parallel() diff --git a/tasks/soon/drafts/extended-sub-command-descriptions.md b/tasks/soon/drafts/extended-sub-command-descriptions.md new file mode 100644 index 0000000..1615dcb --- /dev/null +++ b/tasks/soon/drafts/extended-sub-command-descriptions.md @@ -0,0 +1,15 @@ +--- +type: task +--- + +# Extended Sub-Command Descriptions + +It might be useful to have some kind of extended description for each +sub-command, something that would be displayed only as part of `-h`. + +The extended description could provide more context for what the sub-command is +for, and how it interact with other related sub-commands. + +This could also be a good place to provide usage examples, although maybe those +should go in yet another section, EXAMPLES, to distinguish them from +DESCRIPTION. diff --git a/tasks/v0.0.3/code/public-addr.md b/tasks/v0.0.3/code/public-addr.md new file mode 100644 index 0000000..e4f476b --- /dev/null +++ b/tasks/v0.0.3/code/public-addr.md @@ -0,0 +1,7 @@ +--- +type: task +--- + +# Add `vpn public-addr set`, and `get` Sub-Commands. + +`get` should return an error if no public address is set.