소스 검색

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 11 년 전
부모
커밋
bf7f360dca

+ 1 - 1
daemon/execdriver/lxc/init.go

@@ -88,7 +88,7 @@ func setupNetworking(args *execdriver.InitArgs) error {
 			return fmt.Errorf("Unable to set up networking, %s is not a valid gateway IP", args.Gateway)
 		}
 
-		if err := netlink.AddDefaultGw(gw); err != nil {
+		if err := netlink.AddDefaultGw(gw.String(), "eth0"); err != nil {
 			return fmt.Errorf("Unable to set up networking: %v", err)
 		}
 	}

+ 24 - 0
pkg/libcontainer/container.go

@@ -46,6 +46,9 @@ type Container struct {
 	// Networks specifies the container's network setup to be created
 	Networks []*Network `json:"networks,omitempty"`
 
+	// Routes can be specified to create entries in the route table as the container is started
+	Routes []*Route `json:"routes,omitempty"`
+
 	// Cgroups specifies specific cgroup settings for the various subsystems that the container is
 	// placed into to limit the resources the container has available
 	Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"`
@@ -91,3 +94,24 @@ type Network struct {
 	// container's interfaces if a pair is created, specifically in the case of type veth
 	Mtu int `json:"mtu,omitempty"`
 }
+
+// Routes can be specified to create entries in the route table as the container is started
+//
+// All of destination, source, and gateway should be either IPv4 or IPv6.
+// One of the three options must be present, and ommitted entries will use their
+// IP family default for the route table.  For IPv4 for example, setting the
+// gateway to 1.2.3.4 and the interface to eth0 will set up a standard
+// destination of 0.0.0.0(or *) when viewed in the route table.
+type Route struct {
+	// Sets the destination and mask, should be a CIDR.  Accepts IPv4 and IPv6
+	Destination string `json:"destination,omitempty"`
+
+	// Sets the source and mask, should be a CIDR.  Accepts IPv4 and IPv6
+	Source string `json:"source,omitempty"`
+
+	// Sets the gateway.  Accepts IPv4 and IPv6
+	Gateway string `json:"gateway,omitempty"`
+
+	// The device to set this route up for, for example: eth0
+	InterfaceName string `json:"interface_name,omitempty"`
+}

+ 10 - 0
pkg/libcontainer/container.json

@@ -24,6 +24,16 @@
       "mtu": 1500
     }
   ],
+  "routes": [
+    {
+      "gateway": "172.17.42.1",
+      "interface_name": "eth0"
+    },
+    {
+      "destination": "192.168.0.0/24",
+      "interface_name": "eth0"
+    }
+  ],
   "capabilities": [
     "MKNOD"
   ],

+ 5 - 0
pkg/libcontainer/container_test.go

@@ -39,6 +39,11 @@ func TestContainerJsonFormat(t *testing.T) {
 		t.Fail()
 	}
 
+	if len(container.Routes) != 2 {
+		t.Log("should have found 2 routes")
+		t.Fail()
+	}
+
 	if !container.Namespaces["NEWNET"] {
 		t.Log("namespaces should contain NEWNET")
 		t.Fail()

+ 2 - 2
pkg/libcontainer/network/network.go

@@ -53,8 +53,8 @@ func SetInterfaceMaster(name, master string) error {
 	return netlink.AddToBridge(iface, masterIface)
 }
 
-func SetDefaultGateway(ip string) error {
-	return netlink.AddDefaultGw(net.ParseIP(ip))
+func SetDefaultGateway(ip, ifaceName string) error {
+	return netlink.AddDefaultGw(ip, ifaceName)
 }
 
 func SetInterfaceIp(name string, rawIp string) error {

+ 12 - 10
pkg/libcontainer/network/veth.go

@@ -12,6 +12,8 @@ import (
 type Veth struct {
 }
 
+const defaultDevice = "eth0"
+
 func (v *Veth) Create(n *libcontainer.Network, nspid int, context libcontainer.Context) error {
 	var (
 		bridge string
@@ -56,21 +58,21 @@ func (v *Veth) Initialize(config *libcontainer.Network, context libcontainer.Con
 	if err := InterfaceDown(vethChild); err != nil {
 		return fmt.Errorf("interface down %s %s", vethChild, err)
 	}
-	if err := ChangeInterfaceName(vethChild, "eth0"); err != nil {
-		return fmt.Errorf("change %s to eth0 %s", vethChild, err)
+	if err := ChangeInterfaceName(vethChild, defaultDevice); err != nil {
+		return fmt.Errorf("change %s to %s %s", vethChild, defaultDevice, err)
 	}
-	if err := SetInterfaceIp("eth0", config.Address); err != nil {
-		return fmt.Errorf("set eth0 ip %s", err)
+	if err := SetInterfaceIp(defaultDevice, config.Address); err != nil {
+		return fmt.Errorf("set %s ip %s", defaultDevice, err)
 	}
-	if err := SetMtu("eth0", config.Mtu); err != nil {
-		return fmt.Errorf("set eth0 mtu to %d %s", config.Mtu, err)
+	if err := SetMtu(defaultDevice, config.Mtu); err != nil {
+		return fmt.Errorf("set %s mtu to %d %s", defaultDevice, config.Mtu, err)
 	}
-	if err := InterfaceUp("eth0"); err != nil {
-		return fmt.Errorf("eth0 up %s", err)
+	if err := InterfaceUp(defaultDevice); err != nil {
+		return fmt.Errorf("%s up %s", defaultDevice, err)
 	}
 	if config.Gateway != "" {
-		if err := SetDefaultGateway(config.Gateway); err != nil {
-			return fmt.Errorf("set gateway to %s %s", config.Gateway, err)
+		if err := SetDefaultGateway(config.Gateway, defaultDevice); err != nil {
+			return fmt.Errorf("set gateway to %s on device %s failed with %s", config.Gateway, defaultDevice, err)
 		}
 	}
 	return nil

+ 13 - 0
pkg/libcontainer/nsinit/init.go

@@ -18,6 +18,7 @@ import (
 	"github.com/dotcloud/docker/pkg/libcontainer/security/capabilities"
 	"github.com/dotcloud/docker/pkg/libcontainer/security/restrict"
 	"github.com/dotcloud/docker/pkg/libcontainer/utils"
+	"github.com/dotcloud/docker/pkg/netlink"
 	"github.com/dotcloud/docker/pkg/system"
 	"github.com/dotcloud/docker/pkg/user"
 )
@@ -60,6 +61,9 @@ func Init(container *libcontainer.Container, uncleanRootfs, consolePath string,
 	if err := setupNetwork(container, context); err != nil {
 		return fmt.Errorf("setup networking %s", err)
 	}
+	if err := setupRoute(container); err != nil {
+		return fmt.Errorf("setup route %s", err)
+	}
 
 	label.Init()
 
@@ -168,6 +172,15 @@ func setupNetwork(container *libcontainer.Container, context libcontainer.Contex
 	return nil
 }
 
+func setupRoute(container *libcontainer.Container) error {
+	for _, config := range container.Routes {
+		if err := netlink.AddRoute(config.Destination, config.Source, config.Gateway, config.InterfaceName); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 // FinalizeNamespace drops the caps, sets the correct user
 // and working dir, and closes any leaky file descriptors
 // before execing the command inside the namespace

+ 94 - 17
pkg/netlink/netlink_linux.go

@@ -131,10 +131,9 @@ type RtMsg struct {
 	syscall.RtMsg
 }
 
-func newRtMsg(family int) *RtMsg {
+func newRtMsg() *RtMsg {
 	return &RtMsg{
 		RtMsg: syscall.RtMsg{
-			Family:   uint8(family),
 			Table:    syscall.RT_TABLE_MAIN,
 			Scope:    syscall.RT_SCOPE_UNIVERSE,
 			Protocol: syscall.RTPROT_BOOT,
@@ -367,40 +366,118 @@ done:
 	return nil
 }
 
-// Add a new default gateway. Identical to:
-// ip route add default via $ip
-func AddDefaultGw(ip net.IP) error {
+// Add a new route table entry.
+func AddRoute(destination, source, gateway, device string) error {
+	if destination == "" && source == "" && gateway == "" {
+		return fmt.Errorf("one of destination, source or gateway must not be blank")
+	}
+
 	s, err := getNetlinkSocket()
 	if err != nil {
 		return err
 	}
 	defer s.Close()
 
-	family := getIpFamily(ip)
-
 	wb := newNetlinkRequest(syscall.RTM_NEWROUTE, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
+	msg := newRtMsg()
+	currentFamily := -1
+	var rtAttrs []*RtAttr
 
-	msg := newRtMsg(family)
-	wb.AddData(msg)
+	if destination != "" {
+		destIP, destNet, err := net.ParseCIDR(destination)
+		if err != nil {
+			return fmt.Errorf("destination CIDR %s couldn't be parsed", destination)
+		}
+		destFamily := getIpFamily(destIP)
+		currentFamily = destFamily
+		destLen, bits := destNet.Mask.Size()
+		if destLen == 0 && bits == 0 {
+			return fmt.Errorf("destination CIDR %s generated a non-canonical Mask", destination)
+		}
+		msg.Family = uint8(destFamily)
+		msg.Dst_len = uint8(destLen)
+		var destData []byte
+		if destFamily == syscall.AF_INET {
+			destData = destIP.To4()
+		} else {
+			destData = destIP.To16()
+		}
+		rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_DST, destData))
+	}
 
-	var ipData []byte
-	if family == syscall.AF_INET {
-		ipData = ip.To4()
-	} else {
-		ipData = ip.To16()
+	if source != "" {
+		srcIP, srcNet, err := net.ParseCIDR(source)
+		if err != nil {
+			return fmt.Errorf("source CIDR %s couldn't be parsed", source)
+		}
+		srcFamily := getIpFamily(srcIP)
+		if currentFamily != -1 && currentFamily != srcFamily {
+			return fmt.Errorf("source and destination ip were not the same IP family")
+		}
+		currentFamily = srcFamily
+		srcLen, bits := srcNet.Mask.Size()
+		if srcLen == 0 && bits == 0 {
+			return fmt.Errorf("source CIDR %s generated a non-canonical Mask", source)
+		}
+		msg.Family = uint8(srcFamily)
+		msg.Src_len = uint8(srcLen)
+		var srcData []byte
+		if srcFamily == syscall.AF_INET {
+			srcData = srcIP.To4()
+		} else {
+			srcData = srcIP.To16()
+		}
+		rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_SRC, srcData))
 	}
 
-	gateway := newRtAttr(syscall.RTA_GATEWAY, ipData)
+	if gateway != "" {
+		gwIP := net.ParseIP(gateway)
+		if gwIP == nil {
+			return fmt.Errorf("gateway IP %s couldn't be parsed", gateway)
+		}
+		gwFamily := getIpFamily(gwIP)
+		if currentFamily != -1 && currentFamily != gwFamily {
+			return fmt.Errorf("gateway, source, and destination ip were not the same IP family")
+		}
+		msg.Family = uint8(gwFamily)
+		var gwData []byte
+		if gwFamily == syscall.AF_INET {
+			gwData = gwIP.To4()
+		} else {
+			gwData = gwIP.To16()
+		}
+		rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_GATEWAY, gwData))
+	}
 
-	wb.AddData(gateway)
+	wb.AddData(msg)
+	for _, attr := range rtAttrs {
+		wb.AddData(attr)
+	}
 
-	if err := s.Send(wb); err != nil {
+	var (
+		native = nativeEndian()
+		b      = make([]byte, 4)
+	)
+	iface, err := net.InterfaceByName(device)
+	if err != nil {
 		return err
 	}
+	native.PutUint32(b, uint32(iface.Index))
+
+	wb.AddData(newRtAttr(syscall.RTA_OIF, b))
 
+	if err := s.Send(wb); err != nil {
+		return err
+	}
 	return s.HandleAck(wb.Seq)
 }
 
+// Add a new default gateway. Identical to:
+// ip route add default via $ip
+func AddDefaultGw(ip, device string) error {
+	return AddRoute("", "", ip, device)
+}
+
 // Bring up a particular network interface
 func NetworkLinkUp(iface *net.Interface) error {
 	s, err := getNetlinkSocket()

+ 4 - 1
pkg/netlink/netlink_unsupported.go

@@ -27,9 +27,12 @@ func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
 	return ErrNotImplemented
 }
 
-func AddDefaultGw(ip net.IP) error {
+func AddRoute(destination, source, gateway, device string) error {
 	return ErrNotImplemented
+}
 
+func AddDefaultGw(ip, device string) error {
+	return ErrNotImplemented
 }
 
 func NetworkSetMTU(iface *net.Interface, mtu int) error {