api: Add a field MacAddress to EndpointSettings

Prior to this commit, only container.Config had a MacAddress field and
it's used only for the first network the container connects to. It's a
relic of old times where custom networks were not supported.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
This commit is contained in:
Albin Kerouanton 2023-07-04 23:48:58 +02:00
parent 98323ac114
commit 052562ffd5
No known key found for this signature in database
GPG key ID: 630B8E1DCBDB1864
8 changed files with 95 additions and 11 deletions

View file

@ -1381,6 +1381,7 @@ definitions:
LinkLocalIPs:
- "169.254.34.68"
- "fe80::3468"
MacAddress: "02:42:ac:12:05:02"
Links:
- "container_1"
- "container_2"
@ -2455,6 +2456,11 @@ definitions:
example:
- "container_1"
- "container_2"
MacAddress:
description: |
MAC address for the endpoint on this network. The network driver might ignore this parameter.
type: "string"
example: "02:42:ac:11:00:04"
Aliases:
type: "array"
items:
@ -2505,11 +2511,6 @@ definitions:
type: "integer"
format: "int64"
example: 64
MacAddress:
description: |
MAC address for the endpoint on this network.
type: "string"
example: "02:42:ac:11:00:04"
DriverOpts:
description: |
DriverOpts is a mapping of driver options and values. These options
@ -10130,6 +10131,7 @@ paths:
IPAMConfig:
IPv4Address: "172.24.56.89"
IPv6Address: "2001:db8::5689"
MacAddress: "02:42:ac:12:05:02"
tags: ["Network"]
/networks/{id}/disconnect:

View file

@ -14,6 +14,7 @@ type EndpointSettings struct {
IPAMConfig *EndpointIPAMConfig
Links []string
Aliases []string
MacAddress string
// Operational data
NetworkID string
EndpointID string
@ -23,7 +24,6 @@ type EndpointSettings struct {
IPv6Gateway string
GlobalIPv6Address string
GlobalIPv6PrefixLen int
MacAddress string
DriverOpts map[string]string
}

View file

@ -39,6 +39,9 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
if err := cli.NewVersionError(ctx, "1.44", "specify health-check start interval"); config != nil && config.Healthcheck != nil && config.Healthcheck.StartInterval != 0 && err != nil {
return response, err
}
if err := cli.NewVersionError("1.44", "specify mac-address per network"); hasEndpointSpecificMacAddress(networkingConfig) && err != nil {
return response, err
}
if hostConfig != nil {
if versions.LessThan(cli.ClientVersion(), "1.25") {
@ -91,3 +94,16 @@ func formatPlatform(platform *ocispec.Platform) string {
}
return path.Join(platform.OS, platform.Architecture, platform.Variant)
}
// hasEndpointSpecificMacAddress checks whether one of the endpoint in networkingConfig has a MacAddress defined.
func hasEndpointSpecificMacAddress(networkingConfig *network.NetworkingConfig) bool {
if networkingConfig == nil {
return false
}
for _, endpoint := range networkingConfig.EndpointsConfig {
if endpoint.MacAddress != "" {
return true
}
}
return false
}

View file

@ -628,7 +628,6 @@ func cleanOperationalData(es *network.EndpointSettings) {
es.IPv6Gateway = ""
es.GlobalIPv6Address = ""
es.GlobalIPv6PrefixLen = 0
es.MacAddress = ""
if es.IPAMOperational {
es.IPAMConfig = nil
}

View file

@ -788,6 +788,7 @@ func (daemon *Daemon) clearAttachableNetworks() {
// buildCreateEndpointOptions builds endpoint options from a given network.
func buildCreateEndpointOptions(c *container.Container, n *libnetwork.Network, epConfig *network.EndpointSettings, sb *libnetwork.Sandbox, daemonDNS []string) ([]libnetwork.EndpointOption, error) {
var createOptions []libnetwork.EndpointOption
var genericOptions = make(options.Generic)
nwName := n.Name()
defaultNetName := runconfig.DefaultDaemonNetworkMode().NetworkName()
@ -825,6 +826,14 @@ func buildCreateEndpointOptions(c *container.Container, n *libnetwork.Network, e
for k, v := range epConfig.DriverOpts {
createOptions = append(createOptions, libnetwork.EndpointOptionGeneric(options.Generic{k: v}))
}
if epConfig.MacAddress != "" {
mac, err := net.ParseMAC(epConfig.MacAddress)
if err != nil {
return nil, err
}
genericOptions[netlabel.MacAddress] = mac
}
}
if svcCfg := c.NetworkSettings.Service; svcCfg != nil {
@ -863,9 +872,8 @@ func buildCreateEndpointOptions(c *container.Container, n *libnetwork.Network, e
if err != nil {
return nil, err
}
createOptions = append(createOptions, libnetwork.EndpointOptionGeneric(options.Generic{
netlabel.MacAddress: mac,
}))
genericOptions[netlabel.MacAddress] = mac
}
}
@ -940,7 +948,10 @@ func buildCreateEndpointOptions(c *container.Container, n *libnetwork.Network, e
createOptions = append(createOptions, libnetwork.CreateOptionDNS(daemonDNS))
}
createOptions = append(createOptions, libnetwork.CreateOptionPortMapping(publishedPorts), libnetwork.CreateOptionExposedPorts(exposedPorts))
createOptions = append(createOptions,
libnetwork.CreateOptionPortMapping(publishedPorts),
libnetwork.CreateOptionExposedPorts(exposedPorts),
libnetwork.EndpointOptionGeneric(genericOptions))
return createOptions, nil
}

View file

@ -55,6 +55,8 @@ keywords: "API, Docker, rcli, REST, documentation"
* `POST /services/create` and `POST /services/{id}/update` now accept `Seccomp`
and `AppArmor` fields in the `ContainerSpec.Privileges` object. This allows
some configuration of Seccomp and AppArmor in Swarm services.
* A new endpoint-specific `MacAddress` field has been added to `NetworkSettings.EndpointSettings`
on `POST /containers/create`, and to `EndpointConfig` on `POST /networks/{id}/connect`.
## v1.43 API changes

View file

@ -1,10 +1,12 @@
package container // import "github.com/docker/docker/integration/container"
import (
"bufio"
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"testing"
"time"
@ -14,6 +16,7 @@ import (
"github.com/docker/docker/client"
"github.com/docker/docker/errdefs"
ctr "github.com/docker/docker/integration/internal/container"
net "github.com/docker/docker/integration/internal/network"
"github.com/docker/docker/oci"
"github.com/docker/docker/testutil"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -631,3 +634,42 @@ func TestCreateWithMultipleEndpointSettings(t *testing.T) {
})
}
}
func TestCreateWithCustomMACs(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.44"), "requires API v1.44")
ctx := setupTest(t)
apiClient := testEnv.APIClient()
net.CreateNoError(ctx, t, apiClient, "testnet")
attachCtx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
res := ctr.RunAttach(attachCtx, t, apiClient,
ctr.WithCmd("ip", "-o", "link", "show"),
ctr.WithNetworkMode("bridge"),
ctr.WithMacAddress("bridge", "02:32:1c:23:00:04"))
assert.Equal(t, res.ExitCode, 0)
assert.Equal(t, res.Stderr.String(), "")
scanner := bufio.NewScanner(res.Stdout)
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
// The expected output is:
// 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000\ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
// 134: eth0@if135: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1400 qdisc noqueue \ link/ether 02:42:ac:11:00:04 brd ff:ff:ff:ff:ff:ff
if len(fields) < 11 {
continue
}
ifaceName := fields[1]
if ifaceName[:3] != "eth" {
continue
}
mac := fields[len(fields)-3]
assert.Equal(t, mac, "02:32:1c:23:00:04")
}
}

View file

@ -114,6 +114,18 @@ func WithTmpfs(targetAndOpts string) func(config *TestContainerConfig) {
}
}
func WithMacAddress(networkName, mac string) func(config *TestContainerConfig) {
return func(c *TestContainerConfig) {
if c.NetworkingConfig.EndpointsConfig == nil {
c.NetworkingConfig.EndpointsConfig = map[string]*network.EndpointSettings{}
}
if v, ok := c.NetworkingConfig.EndpointsConfig[networkName]; !ok || v == nil {
c.NetworkingConfig.EndpointsConfig[networkName] = &network.EndpointSettings{}
}
c.NetworkingConfig.EndpointsConfig[networkName].MacAddress = mac
}
}
// WithIPv4 sets the specified ip for the specified network of the container
func WithIPv4(networkName, ip string) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {