Allow drivers to supply static routes for interfaces

Signed-off-by: Tom Denham <tom.denham@metaswitch.com>
This commit is contained in:
Tom Denham 2015-05-19 17:08:56 -07:00 committed by Paul Tiplady
parent db7178a675
commit 65acaaf0b5
11 changed files with 274 additions and 2 deletions

View file

@ -104,6 +104,10 @@ type JoinInfo interface {
// SetGatewayIPv6 sets the default IPv6 gateway when a container joins the endpoint.
SetGatewayIPv6(net.IP) error
// AddStaticRoute adds a routes to the sandbox.
// It may be used in addtion to or instead of a default gateway (as above).
AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error
// SetHostsPath sets the overriding /etc/hosts path to use for the container.
SetHostsPath(string) error

View file

@ -87,6 +87,7 @@ type testEndpoint struct {
gw6 net.IP
hostsPath string
resolvConfPath string
routes []types.StaticRoute
}
func (te *testEndpoint) Interfaces() []driverapi.InterfaceInfo {
@ -157,6 +158,11 @@ func (te *testEndpoint) SetResolvConfPath(path string) error {
return nil
}
func (te *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error {
te.routes = append(te.routes, types.StaticRoute{destination, routeType, nextHop, interfaceID})
return nil
}
func TestQueryEndpointInfo(t *testing.T) {
testQueryEndpointInfo(t, true)
}

View file

@ -149,6 +149,11 @@ func (test *testEndpoint) SetNames(src string, dst string) error {
return nil
}
func (test *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error {
//TODO
return nil
}
func (test *testEndpoint) ID() int {
return test.id
}

View file

@ -293,6 +293,7 @@ func (ep *endpoint) Join(containerID string, options ...EndpointOption) (*Contai
SrcName: i.srcName,
DstName: i.dstPrefix,
Address: &i.addr,
Routes: i.routes,
}
if i.addrv6.IP.To16() != nil {
iface.AddressIPv6 = &i.addrv6
@ -302,6 +303,13 @@ func (ep *endpoint) Join(containerID string, options ...EndpointOption) (*Contai
return nil, err
}
}
// Set up non-interface routes.
for _, r := range ep.joinInfo.StaticRoutes {
err = sb.AddStaticRoute(r)
if err != nil {
return nil, err
}
}
err = sb.SetGateway(joinInfo.gw)
if err != nil {
@ -360,6 +368,14 @@ func (ep *endpoint) Leave(containerID string, options ...EndpointOption) error {
}
}
// Remove non-interface routes.
for _, r := range ep.joinInfo.StaticRoutes {
err = sb.RemoveStaticRoute(r)
if err != nil {
logrus.Debugf("Remove route failed: %v", err)
}
}
ctrlr.sandboxRm(container.data.SandboxKey)
return err

View file

@ -46,6 +46,7 @@ type endpointInterface struct {
addrv6 net.IPNet
srcName string
dstPrefix string
routes []*net.IPNet
}
type endpointJoinInfo struct {
@ -53,6 +54,7 @@ type endpointJoinInfo struct {
gw6 net.IP
hostsPath string
resolvConfPath string
StaticRoutes []*types.StaticRoute
}
func (ep *endpoint) Info() EndpointInfo {
@ -149,6 +151,35 @@ func (ep *endpoint) InterfaceNames() []driverapi.InterfaceNameInfo {
return iList
}
func (ep *endpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error {
ep.Lock()
defer ep.Unlock()
r := types.StaticRoute{destination, routeType, nextHop, interfaceID}
if routeType == types.NEXTHOP {
// If the route specifies a next-hop, then it's loosely routed (i.e. not bound to a particular interface).
ep.joinInfo.StaticRoutes = append(ep.joinInfo.StaticRoutes, &r)
} else {
// If the route doesn't specify a next-hop, it must be a connected route, bound to an interface.
if err := ep.addInterfaceRoute(&r); err != nil {
return err
}
}
return nil
}
func (ep *endpoint) addInterfaceRoute(route *types.StaticRoute) error {
for _, iface := range ep.iFaces {
if iface.id == route.InterfaceID {
iface.routes = append(iface.routes, route.Destination)
return nil
}
}
return types.BadRequestErrorf("Interface with ID %d doesn't exist.",
route.InterfaceID)
}
func (ep *endpoint) SandboxKey() string {
ep.Lock()
defer ep.Unlock()

View file

@ -19,6 +19,7 @@ func configureInterface(iface netlink.Link, settings *Interface) error {
{setInterfaceName, fmt.Sprintf("error renaming interface %q to %q", ifaceName, settings.DstName)},
{setInterfaceIP, fmt.Sprintf("error setting interface %q IP to %q", ifaceName, settings.Address)},
{setInterfaceIPv6, fmt.Sprintf("error setting interface %q IPv6 to %q", ifaceName, settings.AddressIPv6)},
{setInterfaceRoutes, fmt.Sprintf("error setting interface %q routes to %q", ifaceName, settings.Routes)},
}
for _, config := range ifaceConfigurators {
@ -63,6 +64,78 @@ func programGateway(path string, gw net.IP) error {
})
}
// Program a route in to the namespace routing table.
func programRoute(path string, dest *net.IPNet, nh net.IP) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
origns, err := netns.Get()
if err != nil {
return err
}
defer origns.Close()
f, err := os.OpenFile(path, os.O_RDONLY, 0)
if err != nil {
return fmt.Errorf("failed get network namespace %q: %v", path, err)
}
defer f.Close()
nsFD := f.Fd()
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
return err
}
defer netns.Set(origns)
gwRoutes, err := netlink.RouteGet(nh)
if err != nil {
return fmt.Errorf("route for the next hop could not be found: %v", err)
}
return netlink.RouteAdd(&netlink.Route{
Scope: netlink.SCOPE_UNIVERSE,
LinkIndex: gwRoutes[0].LinkIndex,
Gw: gwRoutes[0].Gw,
Dst: dest,
})
}
// Delete a route from the namespace routing table.
func removeRoute(path string, dest *net.IPNet, nh net.IP) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
origns, err := netns.Get()
if err != nil {
return err
}
defer origns.Close()
f, err := os.OpenFile(path, os.O_RDONLY, 0)
if err != nil {
return fmt.Errorf("failed get network namespace %q: %v", path, err)
}
defer f.Close()
nsFD := f.Fd()
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
return err
}
defer netns.Set(origns)
gwRoutes, err := netlink.RouteGet(nh)
if err != nil {
return fmt.Errorf("route for the next hop could not be found: %v", err)
}
return netlink.RouteDel(&netlink.Route{
Scope: netlink.SCOPE_UNIVERSE,
LinkIndex: gwRoutes[0].LinkIndex,
Gw: gwRoutes[0].Gw,
Dst: dest,
})
}
func setInterfaceIP(iface netlink.Link, settings *Interface) error {
ipAddr := &netlink.Addr{IPNet: settings.Address, Label: ""}
return netlink.AddrAdd(iface, ipAddr)
@ -79,3 +152,17 @@ func setInterfaceIPv6(iface netlink.Link, settings *Interface) error {
func setInterfaceName(iface netlink.Link, settings *Interface) error {
return netlink.LinkSetName(iface, settings.DstName)
}
func setInterfaceRoutes(iface netlink.Link, settings *Interface) error {
for _, route := range settings.Routes {
err := netlink.RouteAdd(&netlink.Route{
Scope: netlink.SCOPE_UNIVERSE,
LinkIndex: iface.Attrs().Index,
Dst: route,
})
if err != nil {
return err
}
}
return nil
}

View file

@ -8,6 +8,7 @@ import (
"sync"
"syscall"
"github.com/docker/libnetwork/types"
"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
)
@ -263,6 +264,35 @@ func (n *networkNamespace) SetGatewayIPv6(gw net.IP) error {
return err
}
func (n *networkNamespace) AddStaticRoute(r *types.StaticRoute) error {
err := programRoute(n.path, r.Destination, r.NextHop)
if err == nil {
n.Lock()
n.sinfo.StaticRoutes = append(n.sinfo.StaticRoutes, r)
n.Unlock()
}
return err
}
func (n *networkNamespace) RemoveStaticRoute(r *types.StaticRoute) error {
err := removeRoute(n.path, r.Destination, r.NextHop)
if err == nil {
n.Lock()
lastIndex := len(n.sinfo.StaticRoutes) - 1
for i, v := range n.sinfo.StaticRoutes {
if v == r {
// Overwrite the route we're removing with the last element
n.sinfo.StaticRoutes[i] = n.sinfo.StaticRoutes[lastIndex]
// Shorten the slice to trim the extra element
n.sinfo.StaticRoutes = n.sinfo.StaticRoutes[:lastIndex]
break
}
}
n.Unlock()
}
return err
}
func (n *networkNamespace) Interfaces() []*Interface {
n.Lock()
defer n.Unlock()

View file

@ -35,6 +35,12 @@ type Sandbox interface {
// Set default IPv6 gateway for the sandbox
SetGatewayIPv6(gw net.IP) error
// Add a static route to the sandbox.
AddStaticRoute(*types.StaticRoute) error
// Remove a static route from the sandbox.
RemoveStaticRoute(*types.StaticRoute) error
// Destroy the sandbox
Destroy() error
}
@ -51,7 +57,11 @@ type Info struct {
// IPv6 gateway for the sandbox.
GatewayIPv6 net.IP
// TODO: Add routes and ip tables etc.
// Additional static routes for the sandbox. (Note that directly
// connected routes are stored on the particular interface they refer to.)
StaticRoutes []*types.StaticRoute
// TODO: Add ip tables etc.
}
// Interface represents the settings and identity of a network device. It is
@ -74,15 +84,25 @@ type Interface struct {
// IPv6 address for the interface.
AddressIPv6 *net.IPNet
// IP routes for the interface.
Routes []*net.IPNet
}
// GetCopy returns a copy of this Interface structure
func (i *Interface) GetCopy() *Interface {
copiedRoutes := make([]*net.IPNet, len(i.Routes))
for index := range i.Routes {
copiedRoutes[index] = types.GetIPNetCopy(i.Routes[index])
}
return &Interface{
SrcName: i.SrcName,
DstName: i.DstName,
Address: types.GetIPNetCopy(i.Address),
AddressIPv6: types.GetIPNetCopy(i.AddressIPv6),
Routes: copiedRoutes,
}
}
@ -108,6 +128,16 @@ func (i *Interface) Equal(o *Interface) bool {
return false
}
if len(i.Routes) != len(o.Routes) {
return false
}
for index := range i.Routes {
if !types.CompareIPNet(i.Routes[index], o.Routes[index]) {
return false
}
}
return true
}
@ -120,7 +150,15 @@ func (s *Info) GetCopy() *Info {
gw := types.GetIPCopy(s.Gateway)
gw6 := types.GetIPCopy(s.GatewayIPv6)
return &Info{Interfaces: list, Gateway: gw, GatewayIPv6: gw6}
routes := make([]*types.StaticRoute, len(s.StaticRoutes))
for i, r := range s.StaticRoutes {
routes[i] = r.GetCopy()
}
return &Info{Interfaces: list,
Gateway: gw,
GatewayIPv6: gw6,
StaticRoutes: routes}
}
// Equal checks if this instance of SandboxInfo is equal to the passed one
@ -154,6 +192,17 @@ func (s *Info) Equal(o *Info) bool {
}
}
for index := range s.StaticRoutes {
ss := s.StaticRoutes[index]
oo := o.StaticRoutes[index]
if !types.CompareIPNet(ss.Destination, oo.Destination) {
return false
}
if !ss.NextHop.Equal(oo.NextHop) {
return false
}
}
return true
}

View file

@ -63,6 +63,13 @@ func newInfo(t *testing.T) (*Info, error) {
intf1.AddressIPv6 = addrv6
intf1.AddressIPv6.IP = ip6
_, route, err := net.ParseCIDR("192.168.2.1/32")
if err != nil {
return nil, err
}
intf1.Routes = []*net.IPNet{route}
veth = &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{Name: vethName3, TxQLen: 0},
PeerName: vethName4}
@ -92,6 +99,7 @@ func newInfo(t *testing.T) (*Info, error) {
intf2.AddressIPv6.IP = ip6
sinfo := &Info{Interfaces: []*Interface{intf1, intf2}}
sinfo.Gateway = net.ParseIP("192.168.1.1")
// sinfo.GatewayIPv6 = net.ParseIP("2001:DB8::1")
sinfo.GatewayIPv6 = net.ParseIP("fe80::1")

View file

@ -190,12 +190,14 @@ func getInterfaceList() []*Interface {
DstName: "eth0",
Address: netv4a,
AddressIPv6: netv6a,
Routes: []*net.IPNet{netv4a, netv6a},
},
&Interface{
SrcName: "veth7654321",
DstName: "eth1",
Address: netv4b,
AddressIPv6: netv6b,
Routes: []*net.IPNet{netv4b, netv6b},
},
}
}

View file

@ -184,6 +184,40 @@ func CompareIPNet(a, b *net.IPNet) bool {
return a.IP.Equal(b.IP) && bytes.Equal(a.Mask, b.Mask)
}
const (
// NEXTHOP indicates a StaticRoute with an IP next hop.
NEXTHOP = iota
// CONNECTED indicates a StaticRoute with a interface for directly connected peers.
CONNECTED
)
// StaticRoute is a statically-provisioned IP route.
type StaticRoute struct {
Destination *net.IPNet
RouteType int // NEXT_HOP or CONNECTED
// NextHop will be resolved by the kernel (i.e. as a loose hop).
NextHop net.IP
// InterfaceID must refer to a defined interface on the
// Endpoint to which the routes are specified. Routes specified this way
// are interpreted as directly connected to the specified interface (no
// next hop will be used).
InterfaceID int
}
// GetCopy returns a copy of this StaticRoute structure
func (r *StaticRoute) GetCopy() *StaticRoute {
d := GetIPNetCopy(r.Destination)
nh := GetIPCopy(r.NextHop)
return &StaticRoute{Destination: d,
RouteType: r.RouteType,
NextHop: nh,
InterfaceID: r.InterfaceID}
}
/******************************
* Well-known Error Interfaces
******************************/