Browse code

Fixes #5749 libcontainer support for arbitrary route table entries

Docker-DCO-1.1-Signed-off-by: William Thurston <me@williamthurston.com> (github: jhspaybar)

William Thurston authored on 2014/05/17 16:06:29
Showing 9 changed files
... ...
@@ -88,7 +88,7 @@ func setupNetworking(args *execdriver.InitArgs) error {
88 88
 			return fmt.Errorf("Unable to set up networking, %s is not a valid gateway IP", args.Gateway)
89 89
 		}
90 90
 
91
-		if err := netlink.AddDefaultGw(gw); err != nil {
91
+		if err := netlink.AddDefaultGw(gw.String(), "eth0"); err != nil {
92 92
 			return fmt.Errorf("Unable to set up networking: %v", err)
93 93
 		}
94 94
 	}
... ...
@@ -46,6 +46,9 @@ type Container struct {
46 46
 	// Networks specifies the container's network setup to be created
47 47
 	Networks []*Network `json:"networks,omitempty"`
48 48
 
49
+	// Routes can be specified to create entries in the route table as the container is started
50
+	Routes []*Route `json:"routes,omitempty"`
51
+
49 52
 	// Cgroups specifies specific cgroup settings for the various subsystems that the container is
50 53
 	// placed into to limit the resources the container has available
51 54
 	Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"`
... ...
@@ -91,3 +94,24 @@ type Network struct {
91 91
 	// container's interfaces if a pair is created, specifically in the case of type veth
92 92
 	Mtu int `json:"mtu,omitempty"`
93 93
 }
94
+
95
+// Routes can be specified to create entries in the route table as the container is started
96
+//
97
+// All of destination, source, and gateway should be either IPv4 or IPv6.
98
+// One of the three options must be present, and ommitted entries will use their
99
+// IP family default for the route table.  For IPv4 for example, setting the
100
+// gateway to 1.2.3.4 and the interface to eth0 will set up a standard
101
+// destination of 0.0.0.0(or *) when viewed in the route table.
102
+type Route struct {
103
+	// Sets the destination and mask, should be a CIDR.  Accepts IPv4 and IPv6
104
+	Destination string `json:"destination,omitempty"`
105
+
106
+	// Sets the source and mask, should be a CIDR.  Accepts IPv4 and IPv6
107
+	Source string `json:"source,omitempty"`
108
+
109
+	// Sets the gateway.  Accepts IPv4 and IPv6
110
+	Gateway string `json:"gateway,omitempty"`
111
+
112
+	// The device to set this route up for, for example: eth0
113
+	InterfaceName string `json:"interface_name,omitempty"`
114
+}
... ...
@@ -24,6 +24,16 @@
24 24
       "mtu": 1500
25 25
     }
26 26
   ],
27
+  "routes": [
28
+    {
29
+      "gateway": "172.17.42.1",
30
+      "interface_name": "eth0"
31
+    },
32
+    {
33
+      "destination": "192.168.0.0/24",
34
+      "interface_name": "eth0"
35
+    }
36
+  ],
27 37
   "capabilities": [
28 38
     "MKNOD"
29 39
   ],
... ...
@@ -39,6 +39,11 @@ func TestContainerJsonFormat(t *testing.T) {
39 39
 		t.Fail()
40 40
 	}
41 41
 
42
+	if len(container.Routes) != 2 {
43
+		t.Log("should have found 2 routes")
44
+		t.Fail()
45
+	}
46
+
42 47
 	if !container.Namespaces["NEWNET"] {
43 48
 		t.Log("namespaces should contain NEWNET")
44 49
 		t.Fail()
... ...
@@ -53,8 +53,8 @@ func SetInterfaceMaster(name, master string) error {
53 53
 	return netlink.AddToBridge(iface, masterIface)
54 54
 }
55 55
 
56
-func SetDefaultGateway(ip string) error {
57
-	return netlink.AddDefaultGw(net.ParseIP(ip))
56
+func SetDefaultGateway(ip, ifaceName string) error {
57
+	return netlink.AddDefaultGw(ip, ifaceName)
58 58
 }
59 59
 
60 60
 func SetInterfaceIp(name string, rawIp string) error {
... ...
@@ -12,6 +12,8 @@ import (
12 12
 type Veth struct {
13 13
 }
14 14
 
15
+const defaultDevice = "eth0"
16
+
15 17
 func (v *Veth) Create(n *libcontainer.Network, nspid int, context libcontainer.Context) error {
16 18
 	var (
17 19
 		bridge string
... ...
@@ -56,21 +58,21 @@ func (v *Veth) Initialize(config *libcontainer.Network, context libcontainer.Con
56 56
 	if err := InterfaceDown(vethChild); err != nil {
57 57
 		return fmt.Errorf("interface down %s %s", vethChild, err)
58 58
 	}
59
-	if err := ChangeInterfaceName(vethChild, "eth0"); err != nil {
60
-		return fmt.Errorf("change %s to eth0 %s", vethChild, err)
59
+	if err := ChangeInterfaceName(vethChild, defaultDevice); err != nil {
60
+		return fmt.Errorf("change %s to %s %s", vethChild, defaultDevice, err)
61 61
 	}
62
-	if err := SetInterfaceIp("eth0", config.Address); err != nil {
63
-		return fmt.Errorf("set eth0 ip %s", err)
62
+	if err := SetInterfaceIp(defaultDevice, config.Address); err != nil {
63
+		return fmt.Errorf("set %s ip %s", defaultDevice, err)
64 64
 	}
65
-	if err := SetMtu("eth0", config.Mtu); err != nil {
66
-		return fmt.Errorf("set eth0 mtu to %d %s", config.Mtu, err)
65
+	if err := SetMtu(defaultDevice, config.Mtu); err != nil {
66
+		return fmt.Errorf("set %s mtu to %d %s", defaultDevice, config.Mtu, err)
67 67
 	}
68
-	if err := InterfaceUp("eth0"); err != nil {
69
-		return fmt.Errorf("eth0 up %s", err)
68
+	if err := InterfaceUp(defaultDevice); err != nil {
69
+		return fmt.Errorf("%s up %s", defaultDevice, err)
70 70
 	}
71 71
 	if config.Gateway != "" {
72
-		if err := SetDefaultGateway(config.Gateway); err != nil {
73
-			return fmt.Errorf("set gateway to %s %s", config.Gateway, err)
72
+		if err := SetDefaultGateway(config.Gateway, defaultDevice); err != nil {
73
+			return fmt.Errorf("set gateway to %s on device %s failed with %s", config.Gateway, defaultDevice, err)
74 74
 		}
75 75
 	}
76 76
 	return nil
... ...
@@ -18,6 +18,7 @@ import (
18 18
 	"github.com/dotcloud/docker/pkg/libcontainer/security/capabilities"
19 19
 	"github.com/dotcloud/docker/pkg/libcontainer/security/restrict"
20 20
 	"github.com/dotcloud/docker/pkg/libcontainer/utils"
21
+	"github.com/dotcloud/docker/pkg/netlink"
21 22
 	"github.com/dotcloud/docker/pkg/system"
22 23
 	"github.com/dotcloud/docker/pkg/user"
23 24
 )
... ...
@@ -60,6 +61,9 @@ func Init(container *libcontainer.Container, uncleanRootfs, consolePath string,
60 60
 	if err := setupNetwork(container, context); err != nil {
61 61
 		return fmt.Errorf("setup networking %s", err)
62 62
 	}
63
+	if err := setupRoute(container); err != nil {
64
+		return fmt.Errorf("setup route %s", err)
65
+	}
63 66
 
64 67
 	label.Init()
65 68
 
... ...
@@ -168,6 +172,15 @@ func setupNetwork(container *libcontainer.Container, context libcontainer.Contex
168 168
 	return nil
169 169
 }
170 170
 
171
+func setupRoute(container *libcontainer.Container) error {
172
+	for _, config := range container.Routes {
173
+		if err := netlink.AddRoute(config.Destination, config.Source, config.Gateway, config.InterfaceName); err != nil {
174
+			return err
175
+		}
176
+	}
177
+	return nil
178
+}
179
+
171 180
 // FinalizeNamespace drops the caps, sets the correct user
172 181
 // and working dir, and closes any leaky file descriptors
173 182
 // before execing the command inside the namespace
... ...
@@ -131,10 +131,9 @@ type RtMsg struct {
131 131
 	syscall.RtMsg
132 132
 }
133 133
 
134
-func newRtMsg(family int) *RtMsg {
134
+func newRtMsg() *RtMsg {
135 135
 	return &RtMsg{
136 136
 		RtMsg: syscall.RtMsg{
137
-			Family:   uint8(family),
138 137
 			Table:    syscall.RT_TABLE_MAIN,
139 138
 			Scope:    syscall.RT_SCOPE_UNIVERSE,
140 139
 			Protocol: syscall.RTPROT_BOOT,
... ...
@@ -367,40 +366,118 @@ done:
367 367
 	return nil
368 368
 }
369 369
 
370
-// Add a new default gateway. Identical to:
371
-// ip route add default via $ip
372
-func AddDefaultGw(ip net.IP) error {
370
+// Add a new route table entry.
371
+func AddRoute(destination, source, gateway, device string) error {
372
+	if destination == "" && source == "" && gateway == "" {
373
+		return fmt.Errorf("one of destination, source or gateway must not be blank")
374
+	}
375
+
373 376
 	s, err := getNetlinkSocket()
374 377
 	if err != nil {
375 378
 		return err
376 379
 	}
377 380
 	defer s.Close()
378 381
 
379
-	family := getIpFamily(ip)
380
-
381 382
 	wb := newNetlinkRequest(syscall.RTM_NEWROUTE, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
383
+	msg := newRtMsg()
384
+	currentFamily := -1
385
+	var rtAttrs []*RtAttr
382 386
 
383
-	msg := newRtMsg(family)
384
-	wb.AddData(msg)
387
+	if destination != "" {
388
+		destIP, destNet, err := net.ParseCIDR(destination)
389
+		if err != nil {
390
+			return fmt.Errorf("destination CIDR %s couldn't be parsed", destination)
391
+		}
392
+		destFamily := getIpFamily(destIP)
393
+		currentFamily = destFamily
394
+		destLen, bits := destNet.Mask.Size()
395
+		if destLen == 0 && bits == 0 {
396
+			return fmt.Errorf("destination CIDR %s generated a non-canonical Mask", destination)
397
+		}
398
+		msg.Family = uint8(destFamily)
399
+		msg.Dst_len = uint8(destLen)
400
+		var destData []byte
401
+		if destFamily == syscall.AF_INET {
402
+			destData = destIP.To4()
403
+		} else {
404
+			destData = destIP.To16()
405
+		}
406
+		rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_DST, destData))
407
+	}
385 408
 
386
-	var ipData []byte
387
-	if family == syscall.AF_INET {
388
-		ipData = ip.To4()
389
-	} else {
390
-		ipData = ip.To16()
409
+	if source != "" {
410
+		srcIP, srcNet, err := net.ParseCIDR(source)
411
+		if err != nil {
412
+			return fmt.Errorf("source CIDR %s couldn't be parsed", source)
413
+		}
414
+		srcFamily := getIpFamily(srcIP)
415
+		if currentFamily != -1 && currentFamily != srcFamily {
416
+			return fmt.Errorf("source and destination ip were not the same IP family")
417
+		}
418
+		currentFamily = srcFamily
419
+		srcLen, bits := srcNet.Mask.Size()
420
+		if srcLen == 0 && bits == 0 {
421
+			return fmt.Errorf("source CIDR %s generated a non-canonical Mask", source)
422
+		}
423
+		msg.Family = uint8(srcFamily)
424
+		msg.Src_len = uint8(srcLen)
425
+		var srcData []byte
426
+		if srcFamily == syscall.AF_INET {
427
+			srcData = srcIP.To4()
428
+		} else {
429
+			srcData = srcIP.To16()
430
+		}
431
+		rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_SRC, srcData))
391 432
 	}
392 433
 
393
-	gateway := newRtAttr(syscall.RTA_GATEWAY, ipData)
434
+	if gateway != "" {
435
+		gwIP := net.ParseIP(gateway)
436
+		if gwIP == nil {
437
+			return fmt.Errorf("gateway IP %s couldn't be parsed", gateway)
438
+		}
439
+		gwFamily := getIpFamily(gwIP)
440
+		if currentFamily != -1 && currentFamily != gwFamily {
441
+			return fmt.Errorf("gateway, source, and destination ip were not the same IP family")
442
+		}
443
+		msg.Family = uint8(gwFamily)
444
+		var gwData []byte
445
+		if gwFamily == syscall.AF_INET {
446
+			gwData = gwIP.To4()
447
+		} else {
448
+			gwData = gwIP.To16()
449
+		}
450
+		rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_GATEWAY, gwData))
451
+	}
394 452
 
395
-	wb.AddData(gateway)
453
+	wb.AddData(msg)
454
+	for _, attr := range rtAttrs {
455
+		wb.AddData(attr)
456
+	}
396 457
 
397
-	if err := s.Send(wb); err != nil {
458
+	var (
459
+		native = nativeEndian()
460
+		b      = make([]byte, 4)
461
+	)
462
+	iface, err := net.InterfaceByName(device)
463
+	if err != nil {
398 464
 		return err
399 465
 	}
466
+	native.PutUint32(b, uint32(iface.Index))
467
+
468
+	wb.AddData(newRtAttr(syscall.RTA_OIF, b))
400 469
 
470
+	if err := s.Send(wb); err != nil {
471
+		return err
472
+	}
401 473
 	return s.HandleAck(wb.Seq)
402 474
 }
403 475
 
476
+// Add a new default gateway. Identical to:
477
+// ip route add default via $ip
478
+func AddDefaultGw(ip, device string) error {
479
+	return AddRoute("", "", ip, device)
480
+}
481
+
404 482
 // Bring up a particular network interface
405 483
 func NetworkLinkUp(iface *net.Interface) error {
406 484
 	s, err := getNetlinkSocket()
... ...
@@ -27,9 +27,12 @@ func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
27 27
 	return ErrNotImplemented
28 28
 }
29 29
 
30
-func AddDefaultGw(ip net.IP) error {
30
+func AddRoute(destination, source, gateway, device string) error {
31 31
 	return ErrNotImplemented
32
+}
32 33
 
34
+func AddDefaultGw(ip, device string) error {
35
+	return ErrNotImplemented
33 36
 }
34 37
 
35 38
 func NetworkSetMTU(iface *net.Interface, mtu int) error {