Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only restore a configured MAC addr on restart. #47233

Merged
merged 2 commits into from Feb 2, 2024

Conversation

robmry
Copy link
Contributor

@robmry robmry commented Jan 26, 2024

- What I did

The API's EndpointConfig struct has a MacAddress field that's used for both the configured address, and the current address (which may be generated).

A configured address must be restored when a container is restarted, but a generated address must not.

The previous attempt to differentiate between the two, without adding a field to the API's EndpointConfig that would show up in 'inspect' output, was a field in the daemon's version of EndpointSettings, MACOperational. It did not work, MACOperational was set to 'true' when a configured address was used. So, it ensured addresses were regenerated, but failed to preserve a configured address.

Also, only fill in Config.MacAddress in 'inspect' output when the MAC address is configured.

Fixes #47228
Fixes #47146

- How I did it

So, this change removes code from #47168, and instead adds DesiredMacAddress to the wrapped version of EndpointSettings, which are persisted but do not form part of the 'inspect' output.

Its value is copied from MacAddress (the API field) when a container is created.

The second git commit fills in Config.MacAddress in inspect output using DesiredMacAddress, which is empty when the address is generated. It also ensures the field is filled in for a configured address on the default bridge network, before the container is started, by translating the network name default into bridge.

- How to verify it

New tests added.

On Windows - run a container like this ...

docker run -ti --mac-address 00-11-22-33-44-55 mcr.microsoft.com/windows/nanoserver:ltsc2022 cmd.exe

... and use ipconfig /all to check that the configured MAC address has been used. Also check inspect shows the correct address.

Stop the container, restart the daemon, and restart the container. Then, exec into the container, check that the configured MAC address is still in-use - and that the inspect output is still correct.

This should be automated - #47283

- Description for the changelog

Ensure that a generated MAC address is not restored when a container is restarted, but a configured MAC address is preserved.

Containers created using 25.0.0 may have duplicate MAC addresses, they must be re-created.

Containers created using 25.0.0 or 25.0.1 with user-defined MAC addresses will get generated MAC addresses when they are started using 25.0.2. They must also be re-created.

@robmry robmry marked this pull request as ready for review January 29, 2024 09:16
// API param field) when the container is created, and cleared in the inspect
// output so that only MacAddress is returned to clients. DesiredMacAddress is
// used to set the value for netlabel.MacAddress passed to the driver.
DesiredMacAddress string `json:",omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd even not expose it through the API, otherwise we'll have to support it until the API version v1.44 is dropped:

Suggested change
DesiredMacAddress string `json:",omitempty"`
DesiredMacAddress string `json:"-"`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That'd stop it being encoded for persistent storage... so its value wouldn't be preserved over a daemon restart.

So, the idea is to deal with that by including it in the JSON when it has a value - but always clear it in the copy used for 'inspect' responses.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, right. I totally forgot we're using the same json tag for both HTTP and storage concerns.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why couldn't this field be added to EndpointSettings?

// EndpointSettings is a package local wrapper for
// networktypes.EndpointSettings which stores Endpoint state that
// needs to be persisted to disk but not exposed in the api.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd forgotten / not-noticed that the comment says those extra fields are persisted - but I don't think they are?

I could be wrong but, I think, the wrapper is always reconstructed here when the container's started ...

container.NetworkSettings.Networks[nwName] = &network.EndpointSettings{
EndpointSettings: endpointConfig,
IPAMOperational: operIPAM,
MACOperational: operMAC,
}

Also, the local-wrapper fields aren't available to the daemon's 'inspect' code - so it couldn't differentiate between a generated address and a configured one when it needed to fill in the container-wide Config.MacAddress.

(In the first PR, I got the logic wrong for deciding whether the address was configured - it wasn't as similar to the operIPAM case as we thought, so operMAC wasn't set up properly. And because the field isn't persisted, I don't think it was possible to tell the difference between a generated MAC and a configured one during the container-run, it has to be spotted and remembered during the create.)

If that seems right, I'll fix the comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd forgotten / not-noticed that the comment says those extra fields are persisted - but I don't think they are?

They're persisted in the container config: (container.Container).NetworkSettings[].Networks[].

I could be wrong but, I think, the wrapper is always reconstructed here when the container's started ...

So? That could be changed.

Also, the local-wrapper fields aren't available to the daemon's 'inspect' code - so it couldn't differentiate between a generated address and a configured one when it needed to fill in the container-wide Config.MacAddress.

The local-wrapper fields are accessible from the *Container. I find it hard to believe that they are not available to the code which maps an ID to a *Container and then to an API response.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thank you - that helped me to untangle it a bit.

@@ -24,6 +28,7 @@ type EndpointSettings struct {
IPv6Gateway string
GlobalIPv6Address string
GlobalIPv6PrefixLen int
MacAddress string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to move that field, it's still what should be used by API clients to configure a container's MAC address.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was that after the value from the API has been copied into DesiredMacAddress, the MacAddress field does become operational data - it's reset in cleanOperationalData(), along with the rest.

But yes, if this struct might be used by someone trying to figure out what to put in the API request, I can move it back. (It's bound to be a bit confusing until we properly separate configured state from running state.)

daemon/inspect.go Outdated Show resolved Hide resolved
@robmry robmry force-pushed the 47146-duplicate_mac_addrs2 branch 2 times, most recently from 51ce31b to 9e387e5 Compare January 29, 2024 14:43
@robmry robmry requested review from corhere and removed request for thaJeztah January 29, 2024 18:50
@robmry robmry force-pushed the 47146-duplicate_mac_addrs2 branch 2 times, most recently from c113c51 to 98a0d0d Compare January 30, 2024 16:24
Copy link
Contributor

@corhere corhere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything not commented on LGTM

api/types/network/endpoint.go Outdated Show resolved Hide resolved
daemon/container_operations.go Outdated Show resolved Hide resolved
daemon/network/settings.go Show resolved Hide resolved
// Check that a configured MAC address is restored after a container restart,
// and after a daemon restart.
func TestCfgdMACAddrOnRestart(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the changes applicable to Windows containers? How feasible would it be to add Windows integration tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue with a MAC address generated by the bridge n/w driver getting reused after a container restart, then becoming a duplicate, doesn't apply on native-Windows - there's no bridge driver. (And these tests are all bridge-network based.)

Do we have any networking integration tests that start containers on Windows? There's probably a gap we should try to fill.

But, I think it has to be out of scope for this bug fix?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests use the bridge driver, but are the daemon code paths being touched specific to bridge, or Linux? Because it looks suspiciously like the code is cross-platform.

if opt, ok := epOptions[netlabel.MacAddress]; ok {
if mac, ok := opt.(net.HardwareAddr); ok {
ec.MacAddress = mac
} else {
return nil, fmt.Errorf("Invalid endpoint configuration")
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no way to repro the Linux duplicate MAC address problem, because Windows MAC address generation doesn't work like that (addresses seem to be random, not based on IPAM-assigned IP addresses).

But I have checked that, on Windows without this change, a configured MAC address was not preserved over a daemon restart. With the change, it is.

It should be possible to write an automated regression test for that, and probably not too difficult - but I'd rather deal with it separately ... it's going to take me some time to figure out how to run an integration test on local Windows, and/or a number of pushes to GitHub to get it right.

So, perhaps we can resolve this if I raise an issue saying the regression test is needed, and assign it to myself to have a go at?

Copy link
Contributor

@corhere corhere Jan 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, perhaps we can resolve this if I raise an issue saying the regression test is needed, and assign it to myself to have a go at?

I'd be satisfied with editing the "how to verify" section of the PR description to mention that you manually tested the change on Windows. Filing an issue to add a Windows regression test would be a huge bonus.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good stuff... that's done.

integration/networking/mac_addr_test.go Outdated Show resolved Hide resolved
The API's EndpointConfig struct has a MacAddress field that's used for
both the configured address, and the current address (which may be generated).

A configured address must be restored when a container is restarted, but a
generated address must not.

The previous attempt to differentiate between the two, without adding a field
to the API's EndpointConfig that would show up in 'inspect' output, was a
field in the daemon's version of EndpointSettings, MACOperational. It did
not work, MACOperational was set to true when a configured address was
used. So, while it ensured addresses were regenerated, it failed to preserve
a configured address.

So, this change removes that code, and adds DesiredMacAddress to the wrapped
version of EndpointSettings, where it is persisted but does not appear in
'inspect' results. Its value is copied from MacAddress (the API field) when
a container is created.

Signed-off-by: Rob Murray <rob.murray@docker.com>
Do not set 'Config.MacAddress' in inspect output unless the MAC address
is configured.

Also, make sure it is filled in for a configured address on the default
network before the container is started (by translating the network name
from 'default' to 'config' so that the address lookup works).

Signed-off-by: Rob Murray <rob.murray@docker.com>
Copy link
Member

@akerouanton akerouanton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@akerouanton akerouanton merged commit ca683c1 into moby:master Feb 2, 2024
126 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants