From 44184040260cf297e75984b7191710504b9b70e5 Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Thu, 2 Oct 2014 16:46:06 -0700 Subject: [PATCH] Support for consistent MAC address. Right now, MAC addresses are randomly generated by the kernel when creating the veth interfaces. This causes different issues related to ARP, such as #4581, #5737 and #8269. This change adds support for consistent MAC addresses, guaranteeing that an IP address will always end up with the same MAC address, no matter what. Since IP addresses are already guaranteed to be unique by the IPAllocator, MAC addresses will inherit this property as well for free. Consistent mac addresses is also a requirement for stable networking (#8297) since re-using the same IP address on a different MAC address triggers the ARP issue. Finally, this change makes the MAC address accessible through docker inspect, which fixes #4033. Signed-off-by: Andrea Luzzardi Upstream-commit: 88e21c6a75310da158bbee3a5fdc135697c93ba1 Component: engine --- components/engine/daemon/container.go | 2 ++ components/engine/daemon/execdriver/driver.go | 3 +- .../engine/daemon/execdriver/native/create.go | 1 + components/engine/daemon/network_settings.go | 1 + .../daemon/networkdriver/bridge/driver.go | 32 +++++++++++++++++++ .../networkdriver/bridge/driver_test.go | 16 ++++++++++ 6 files changed, 54 insertions(+), 1 deletion(-) diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index a95815ca73..0d97d726a1 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/daemon/container.go @@ -215,6 +215,7 @@ func populateCommand(c *Container, env []string) error { Bridge: network.Bridge, IPAddress: network.IPAddress, IPPrefixLen: network.IPPrefixLen, + MacAddress: network.MacAddress, } } case "container": @@ -504,6 +505,7 @@ func (container *Container) allocateNetwork() error { container.NetworkSettings.Bridge = env.Get("Bridge") container.NetworkSettings.IPAddress = env.Get("IP") container.NetworkSettings.IPPrefixLen = env.GetInt("IPPrefixLen") + container.NetworkSettings.MacAddress = env.Get("MacAddress") container.NetworkSettings.Gateway = env.Get("Gateway") return nil diff --git a/components/engine/daemon/execdriver/driver.go b/components/engine/daemon/execdriver/driver.go index 0be2d50dc4..22e4c4647c 100644 --- a/components/engine/daemon/execdriver/driver.go +++ b/components/engine/daemon/execdriver/driver.go @@ -65,8 +65,9 @@ type Network struct { type NetworkInterface struct { Gateway string `json:"gateway"` IPAddress string `json:"ip"` - Bridge string `json:"bridge"` IPPrefixLen int `json:"ip_prefix_len"` + MacAddress string `json:"mac_address"` + Bridge string `json:"bridge"` } type Resources struct { diff --git a/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index 7ba69efece..492247e492 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/daemon/execdriver/native/create.go @@ -95,6 +95,7 @@ func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Com vethNetwork := libcontainer.Network{ Mtu: c.Network.Mtu, Address: fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen), + MacAddress: c.Network.Interface.MacAddress, Gateway: c.Network.Interface.Gateway, Type: "veth", Bridge: c.Network.Interface.Bridge, diff --git a/components/engine/daemon/network_settings.go b/components/engine/daemon/network_settings.go index bf28ca1b50..69c15be3db 100644 --- a/components/engine/daemon/network_settings.go +++ b/components/engine/daemon/network_settings.go @@ -11,6 +11,7 @@ type PortMapping map[string]string // Deprecated type NetworkSettings struct { IPAddress string IPPrefixLen int + MacAddress string Gateway string Bridge string PortMapping map[string]PortMapping // Deprecated diff --git a/components/engine/daemon/networkdriver/bridge/driver.go b/components/engine/daemon/networkdriver/bridge/driver.go index 3135f1d42e..e05a2c21a5 100644 --- a/components/engine/daemon/networkdriver/bridge/driver.go +++ b/components/engine/daemon/networkdriver/bridge/driver.go @@ -326,10 +326,36 @@ func createBridgeIface(name string) error { return netlink.CreateBridge(name, setBridgeMacAddr) } +// Generate a IEEE802 compliant MAC address from the given IP address. +// +// The generator is guaranteed to be consistent: the same IP will always yield the same +// MAC address. This is to avoid ARP cache issues. +func generateMacAddr(ip net.IP) net.HardwareAddr { + hw := make(net.HardwareAddr, 6) + + // The first byte of the MAC address has to comply with these rules: + // 1. Unicast: Set the least-significant bit to 0. + // 2. Address is locally administered: Set the second-least-significant bit (U/L) to 1. + // 3. As "small" as possible: The veth address has to be "smaller" than the bridge address. + hw[0] = 0x02 + + // The first 24 bits of the MAC represent the Organizationally Unique Identifier (OUI). + // Since this address is locally administered, we can do whatever we want as long as + // it doesn't conflict with other addresses. + hw[1] = 0x42 + + // Insert the IP address into the last 32 bits of the MAC address. + // This is a simple way to guarantee the address will be consistent and unique. + copy(hw[2:], ip.To4()) + + return hw +} + // Allocate a network interface func Allocate(job *engine.Job) engine.Status { var ( ip net.IP + mac net.HardwareAddr err error id = job.Args[0] requestedIP = net.ParseIP(job.Getenv("RequestedIP")) @@ -344,10 +370,16 @@ func Allocate(job *engine.Job) engine.Status { return job.Error(err) } + // If no explicit mac address was given, generate a random one. + if mac, err = net.ParseMAC(job.Getenv("RequestedMac")); err != nil { + mac = generateMacAddr(ip) + } + out := engine.Env{} out.Set("IP", ip.String()) out.Set("Mask", bridgeNetwork.Mask.String()) out.Set("Gateway", bridgeNetwork.IP.String()) + out.Set("MacAddress", mac.String()) out.Set("Bridge", bridgeIface) size, _ := bridgeNetwork.Mask.Size() diff --git a/components/engine/daemon/networkdriver/bridge/driver_test.go b/components/engine/daemon/networkdriver/bridge/driver_test.go index 883b3a6d21..1bda2f4372 100644 --- a/components/engine/daemon/networkdriver/bridge/driver_test.go +++ b/components/engine/daemon/networkdriver/bridge/driver_test.go @@ -102,3 +102,19 @@ func TestHostnameFormatChecking(t *testing.T) { t.Fatal("Failed to check invalid HostIP") } } + +func TestMacAddrGeneration(t *testing.T) { + ip := net.ParseIP("192.168.0.1") + mac := generateMacAddr(ip).String() + + // Should be consistent. + if generateMacAddr(ip).String() != mac { + t.Fatal("Inconsistent MAC address") + } + + // Should be unique. + ip2 := net.ParseIP("192.168.0.2") + if generateMacAddr(ip2).String() == mac { + t.Fatal("Non-unique MAC address") + } +}