Browse code

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 authored on 2014/10/03 08:46:06
Showing 6 changed files
... ...
@@ -215,6 +215,7 @@ func populateCommand(c *Container, env []string) error {
215 215
 				Bridge:      network.Bridge,
216 216
 				IPAddress:   network.IPAddress,
217 217
 				IPPrefixLen: network.IPPrefixLen,
218
+				MacAddress:  network.MacAddress,
218 219
 			}
219 220
 		}
220 221
 	case "container":
... ...
@@ -504,6 +505,7 @@ func (container *Container) allocateNetwork() error {
504 504
 	container.NetworkSettings.Bridge = env.Get("Bridge")
505 505
 	container.NetworkSettings.IPAddress = env.Get("IP")
506 506
 	container.NetworkSettings.IPPrefixLen = env.GetInt("IPPrefixLen")
507
+	container.NetworkSettings.MacAddress = env.Get("MacAddress")
507 508
 	container.NetworkSettings.Gateway = env.Get("Gateway")
508 509
 
509 510
 	return nil
... ...
@@ -65,8 +65,9 @@ type Network struct {
65 65
 type NetworkInterface struct {
66 66
 	Gateway     string `json:"gateway"`
67 67
 	IPAddress   string `json:"ip"`
68
-	Bridge      string `json:"bridge"`
69 68
 	IPPrefixLen int    `json:"ip_prefix_len"`
69
+	MacAddress  string `json:"mac_address"`
70
+	Bridge      string `json:"bridge"`
70 71
 }
71 72
 
72 73
 type Resources struct {
... ...
@@ -95,6 +95,7 @@ func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Com
95 95
 		vethNetwork := libcontainer.Network{
96 96
 			Mtu:        c.Network.Mtu,
97 97
 			Address:    fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen),
98
+			MacAddress: c.Network.Interface.MacAddress,
98 99
 			Gateway:    c.Network.Interface.Gateway,
99 100
 			Type:       "veth",
100 101
 			Bridge:     c.Network.Interface.Bridge,
... ...
@@ -11,6 +11,7 @@ type PortMapping map[string]string // Deprecated
11 11
 type NetworkSettings struct {
12 12
 	IPAddress   string
13 13
 	IPPrefixLen int
14
+	MacAddress  string
14 15
 	Gateway     string
15 16
 	Bridge      string
16 17
 	PortMapping map[string]PortMapping // Deprecated
... ...
@@ -326,10 +326,36 @@ func createBridgeIface(name string) error {
326 326
 	return netlink.CreateBridge(name, setBridgeMacAddr)
327 327
 }
328 328
 
329
+// Generate a IEEE802 compliant MAC address from the given IP address.
330
+//
331
+// The generator is guaranteed to be consistent: the same IP will always yield the same
332
+// MAC address. This is to avoid ARP cache issues.
333
+func generateMacAddr(ip net.IP) net.HardwareAddr {
334
+	hw := make(net.HardwareAddr, 6)
335
+
336
+	// The first byte of the MAC address has to comply with these rules:
337
+	// 1. Unicast: Set the least-significant bit to 0.
338
+	// 2. Address is locally administered: Set the second-least-significant bit (U/L) to 1.
339
+	// 3. As "small" as possible: The veth address has to be "smaller" than the bridge address.
340
+	hw[0] = 0x02
341
+
342
+	// The first 24 bits of the MAC represent the Organizationally Unique Identifier (OUI).
343
+	// Since this address is locally administered, we can do whatever we want as long as
344
+	// it doesn't conflict with other addresses.
345
+	hw[1] = 0x42
346
+
347
+	// Insert the IP address into the last 32 bits of the MAC address.
348
+	// This is a simple way to guarantee the address will be consistent and unique.
349
+	copy(hw[2:], ip.To4())
350
+
351
+	return hw
352
+}
353
+
329 354
 // Allocate a network interface
330 355
 func Allocate(job *engine.Job) engine.Status {
331 356
 	var (
332 357
 		ip          net.IP
358
+		mac         net.HardwareAddr
333 359
 		err         error
334 360
 		id          = job.Args[0]
335 361
 		requestedIP = net.ParseIP(job.Getenv("RequestedIP"))
... ...
@@ -344,10 +370,16 @@ func Allocate(job *engine.Job) engine.Status {
344 344
 		return job.Error(err)
345 345
 	}
346 346
 
347
+	// If no explicit mac address was given, generate a random one.
348
+	if mac, err = net.ParseMAC(job.Getenv("RequestedMac")); err != nil {
349
+		mac = generateMacAddr(ip)
350
+	}
351
+
347 352
 	out := engine.Env{}
348 353
 	out.Set("IP", ip.String())
349 354
 	out.Set("Mask", bridgeNetwork.Mask.String())
350 355
 	out.Set("Gateway", bridgeNetwork.IP.String())
356
+	out.Set("MacAddress", mac.String())
351 357
 	out.Set("Bridge", bridgeIface)
352 358
 
353 359
 	size, _ := bridgeNetwork.Mask.Size()
... ...
@@ -102,3 +102,19 @@ func TestHostnameFormatChecking(t *testing.T) {
102 102
 		t.Fatal("Failed to check invalid HostIP")
103 103
 	}
104 104
 }
105
+
106
+func TestMacAddrGeneration(t *testing.T) {
107
+	ip := net.ParseIP("192.168.0.1")
108
+	mac := generateMacAddr(ip).String()
109
+
110
+	// Should be consistent.
111
+	if generateMacAddr(ip).String() != mac {
112
+		t.Fatal("Inconsistent MAC address")
113
+	}
114
+
115
+	// Should be unique.
116
+	ip2 := net.ParseIP("192.168.0.2")
117
+	if generateMacAddr(ip2).String() == mac {
118
+		t.Fatal("Non-unique MAC address")
119
+	}
120
+}