Просмотр исходного кода

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 <aluzzardi@gmail.com>
Andrea Luzzardi 10 лет назад
Родитель
Сommit
88e21c6a75

+ 2 - 0
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

+ 2 - 1
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 {

+ 1 - 0
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,

+ 1 - 0
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

+ 32 - 0
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()

+ 16 - 0
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")
+	}
+}