diff --git a/libcontainer/container.go b/libcontainer/container.go index 6734bfd..27a4235 100644 --- a/libcontainer/container.go +++ b/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"` +} diff --git a/libcontainer/container.json b/libcontainer/container.json index 7156260..3487b47 100644 --- a/libcontainer/container.json +++ b/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" ], diff --git a/libcontainer/container_test.go b/libcontainer/container_test.go index f6e991e..23b15d2 100644 --- a/libcontainer/container_test.go +++ b/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() diff --git a/libcontainer/network/network.go b/libcontainer/network/network.go index f8dee45..85a28dc 100644 --- a/libcontainer/network/network.go +++ b/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 { diff --git a/libcontainer/network/veth.go b/libcontainer/network/veth.go index 3df0cd6..d3be221 100644 --- a/libcontainer/network/veth.go +++ b/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 diff --git a/libcontainer/nsinit/init.go b/libcontainer/nsinit/init.go index 3012106..95d07a4 100644 --- a/libcontainer/nsinit/init.go +++ b/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 diff --git a/netlink/netlink_linux.go b/netlink/netlink_linux.go index 6de293d..21d4593 100644 --- a/netlink/netlink_linux.go +++ b/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) - - var ipData []byte - if family == syscall.AF_INET { - ipData = ip.To4() - } else { - ipData = ip.To16() + 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)) } - gateway := newRtAttr(syscall.RTA_GATEWAY, ipData) + 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)) + } - wb.AddData(gateway) + 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(msg) + for _, attr := range rtAttrs { + wb.AddData(attr) + } + + 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() diff --git a/netlink/netlink_unsupported.go b/netlink/netlink_unsupported.go index 8a5531b..1359345 100644 --- a/netlink/netlink_unsupported.go +++ b/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 {