Browse code

Allow drivers to supply static routes for interfaces

Signed-off-by: Tom Denham <tom.denham@metaswitch.com>

Tom Denham authored on 2015/05/20 09:08:56
Showing 11 changed files
... ...
@@ -104,6 +104,10 @@ type JoinInfo interface {
104 104
 	// SetGatewayIPv6 sets the default IPv6 gateway when a container joins the endpoint.
105 105
 	SetGatewayIPv6(net.IP) error
106 106
 
107
+	// AddStaticRoute adds a routes to the sandbox.
108
+	// It may be used in addtion to or instead of a default gateway (as above).
109
+	AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error
110
+
107 111
 	// SetHostsPath sets the overriding /etc/hosts path to use for the container.
108 112
 	SetHostsPath(string) error
109 113
 
... ...
@@ -87,6 +87,7 @@ type testEndpoint struct {
87 87
 	gw6            net.IP
88 88
 	hostsPath      string
89 89
 	resolvConfPath string
90
+	routes         []types.StaticRoute
90 91
 }
91 92
 
92 93
 func (te *testEndpoint) Interfaces() []driverapi.InterfaceInfo {
... ...
@@ -157,6 +158,11 @@ func (te *testEndpoint) SetResolvConfPath(path string) error {
157 157
 	return nil
158 158
 }
159 159
 
160
+func (te *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error {
161
+	te.routes = append(te.routes, types.StaticRoute{destination, routeType, nextHop, interfaceID})
162
+	return nil
163
+}
164
+
160 165
 func TestQueryEndpointInfo(t *testing.T) {
161 166
 	testQueryEndpointInfo(t, true)
162 167
 }
... ...
@@ -149,6 +149,11 @@ func (test *testEndpoint) SetNames(src string, dst string) error {
149 149
 	return nil
150 150
 }
151 151
 
152
+func (test *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error {
153
+	//TODO
154
+	return nil
155
+}
156
+
152 157
 func (test *testEndpoint) ID() int {
153 158
 	return test.id
154 159
 }
... ...
@@ -293,6 +293,7 @@ func (ep *endpoint) Join(containerID string, options ...EndpointOption) (*Contai
293 293
 			SrcName: i.srcName,
294 294
 			DstName: i.dstPrefix,
295 295
 			Address: &i.addr,
296
+			Routes:  i.routes,
296 297
 		}
297 298
 		if i.addrv6.IP.To16() != nil {
298 299
 			iface.AddressIPv6 = &i.addrv6
... ...
@@ -302,6 +303,13 @@ func (ep *endpoint) Join(containerID string, options ...EndpointOption) (*Contai
302 302
 			return nil, err
303 303
 		}
304 304
 	}
305
+	// Set up non-interface routes.
306
+	for _, r := range ep.joinInfo.StaticRoutes {
307
+		err = sb.AddStaticRoute(r)
308
+		if err != nil {
309
+			return nil, err
310
+		}
311
+	}
305 312
 
306 313
 	err = sb.SetGateway(joinInfo.gw)
307 314
 	if err != nil {
... ...
@@ -360,6 +368,14 @@ func (ep *endpoint) Leave(containerID string, options ...EndpointOption) error {
360 360
 		}
361 361
 	}
362 362
 
363
+	// Remove non-interface routes.
364
+	for _, r := range ep.joinInfo.StaticRoutes {
365
+		err = sb.RemoveStaticRoute(r)
366
+		if err != nil {
367
+			logrus.Debugf("Remove route failed: %v", err)
368
+		}
369
+	}
370
+
363 371
 	ctrlr.sandboxRm(container.data.SandboxKey)
364 372
 
365 373
 	return err
... ...
@@ -46,6 +46,7 @@ type endpointInterface struct {
46 46
 	addrv6    net.IPNet
47 47
 	srcName   string
48 48
 	dstPrefix string
49
+	routes    []*net.IPNet
49 50
 }
50 51
 
51 52
 type endpointJoinInfo struct {
... ...
@@ -53,6 +54,7 @@ type endpointJoinInfo struct {
53 53
 	gw6            net.IP
54 54
 	hostsPath      string
55 55
 	resolvConfPath string
56
+	StaticRoutes   []*types.StaticRoute
56 57
 }
57 58
 
58 59
 func (ep *endpoint) Info() EndpointInfo {
... ...
@@ -149,6 +151,35 @@ func (ep *endpoint) InterfaceNames() []driverapi.InterfaceNameInfo {
149 149
 	return iList
150 150
 }
151 151
 
152
+func (ep *endpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error {
153
+	ep.Lock()
154
+	defer ep.Unlock()
155
+
156
+	r := types.StaticRoute{destination, routeType, nextHop, interfaceID}
157
+
158
+	if routeType == types.NEXTHOP {
159
+		// If the route specifies a next-hop, then it's loosely routed (i.e. not bound to a particular interface).
160
+		ep.joinInfo.StaticRoutes = append(ep.joinInfo.StaticRoutes, &r)
161
+	} else {
162
+		// If the route doesn't specify a next-hop, it must be a connected route, bound to an interface.
163
+		if err := ep.addInterfaceRoute(&r); err != nil {
164
+			return err
165
+		}
166
+	}
167
+	return nil
168
+}
169
+
170
+func (ep *endpoint) addInterfaceRoute(route *types.StaticRoute) error {
171
+	for _, iface := range ep.iFaces {
172
+		if iface.id == route.InterfaceID {
173
+			iface.routes = append(iface.routes, route.Destination)
174
+			return nil
175
+		}
176
+	}
177
+	return types.BadRequestErrorf("Interface with ID %d doesn't exist.",
178
+		route.InterfaceID)
179
+}
180
+
152 181
 func (ep *endpoint) SandboxKey() string {
153 182
 	ep.Lock()
154 183
 	defer ep.Unlock()
... ...
@@ -19,6 +19,7 @@ func configureInterface(iface netlink.Link, settings *Interface) error {
19 19
 		{setInterfaceName, fmt.Sprintf("error renaming interface %q to %q", ifaceName, settings.DstName)},
20 20
 		{setInterfaceIP, fmt.Sprintf("error setting interface %q IP to %q", ifaceName, settings.Address)},
21 21
 		{setInterfaceIPv6, fmt.Sprintf("error setting interface %q IPv6 to %q", ifaceName, settings.AddressIPv6)},
22
+		{setInterfaceRoutes, fmt.Sprintf("error setting interface %q routes to %q", ifaceName, settings.Routes)},
22 23
 	}
23 24
 
24 25
 	for _, config := range ifaceConfigurators {
... ...
@@ -63,6 +64,78 @@ func programGateway(path string, gw net.IP) error {
63 63
 	})
64 64
 }
65 65
 
66
+// Program a route in to the namespace routing table.
67
+func programRoute(path string, dest *net.IPNet, nh net.IP) error {
68
+	runtime.LockOSThread()
69
+	defer runtime.UnlockOSThread()
70
+
71
+	origns, err := netns.Get()
72
+	if err != nil {
73
+		return err
74
+	}
75
+	defer origns.Close()
76
+
77
+	f, err := os.OpenFile(path, os.O_RDONLY, 0)
78
+	if err != nil {
79
+		return fmt.Errorf("failed get network namespace %q: %v", path, err)
80
+	}
81
+	defer f.Close()
82
+
83
+	nsFD := f.Fd()
84
+	if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
85
+		return err
86
+	}
87
+	defer netns.Set(origns)
88
+
89
+	gwRoutes, err := netlink.RouteGet(nh)
90
+	if err != nil {
91
+		return fmt.Errorf("route for the next hop could not be found: %v", err)
92
+	}
93
+
94
+	return netlink.RouteAdd(&netlink.Route{
95
+		Scope:     netlink.SCOPE_UNIVERSE,
96
+		LinkIndex: gwRoutes[0].LinkIndex,
97
+		Gw:        gwRoutes[0].Gw,
98
+		Dst:       dest,
99
+	})
100
+}
101
+
102
+// Delete a route from the namespace routing table.
103
+func removeRoute(path string, dest *net.IPNet, nh net.IP) error {
104
+	runtime.LockOSThread()
105
+	defer runtime.UnlockOSThread()
106
+
107
+	origns, err := netns.Get()
108
+	if err != nil {
109
+		return err
110
+	}
111
+	defer origns.Close()
112
+
113
+	f, err := os.OpenFile(path, os.O_RDONLY, 0)
114
+	if err != nil {
115
+		return fmt.Errorf("failed get network namespace %q: %v", path, err)
116
+	}
117
+	defer f.Close()
118
+
119
+	nsFD := f.Fd()
120
+	if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
121
+		return err
122
+	}
123
+	defer netns.Set(origns)
124
+
125
+	gwRoutes, err := netlink.RouteGet(nh)
126
+	if err != nil {
127
+		return fmt.Errorf("route for the next hop could not be found: %v", err)
128
+	}
129
+
130
+	return netlink.RouteDel(&netlink.Route{
131
+		Scope:     netlink.SCOPE_UNIVERSE,
132
+		LinkIndex: gwRoutes[0].LinkIndex,
133
+		Gw:        gwRoutes[0].Gw,
134
+		Dst:       dest,
135
+	})
136
+}
137
+
66 138
 func setInterfaceIP(iface netlink.Link, settings *Interface) error {
67 139
 	ipAddr := &netlink.Addr{IPNet: settings.Address, Label: ""}
68 140
 	return netlink.AddrAdd(iface, ipAddr)
... ...
@@ -79,3 +152,17 @@ func setInterfaceIPv6(iface netlink.Link, settings *Interface) error {
79 79
 func setInterfaceName(iface netlink.Link, settings *Interface) error {
80 80
 	return netlink.LinkSetName(iface, settings.DstName)
81 81
 }
82
+
83
+func setInterfaceRoutes(iface netlink.Link, settings *Interface) error {
84
+	for _, route := range settings.Routes {
85
+		err := netlink.RouteAdd(&netlink.Route{
86
+			Scope:     netlink.SCOPE_UNIVERSE,
87
+			LinkIndex: iface.Attrs().Index,
88
+			Dst:       route,
89
+		})
90
+		if err != nil {
91
+			return err
92
+		}
93
+	}
94
+	return nil
95
+}
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"sync"
9 9
 	"syscall"
10 10
 
11
+	"github.com/docker/libnetwork/types"
11 12
 	"github.com/vishvananda/netlink"
12 13
 	"github.com/vishvananda/netns"
13 14
 )
... ...
@@ -263,6 +264,35 @@ func (n *networkNamespace) SetGatewayIPv6(gw net.IP) error {
263 263
 	return err
264 264
 }
265 265
 
266
+func (n *networkNamespace) AddStaticRoute(r *types.StaticRoute) error {
267
+	err := programRoute(n.path, r.Destination, r.NextHop)
268
+	if err == nil {
269
+		n.Lock()
270
+		n.sinfo.StaticRoutes = append(n.sinfo.StaticRoutes, r)
271
+		n.Unlock()
272
+	}
273
+	return err
274
+}
275
+
276
+func (n *networkNamespace) RemoveStaticRoute(r *types.StaticRoute) error {
277
+	err := removeRoute(n.path, r.Destination, r.NextHop)
278
+	if err == nil {
279
+		n.Lock()
280
+		lastIndex := len(n.sinfo.StaticRoutes) - 1
281
+		for i, v := range n.sinfo.StaticRoutes {
282
+			if v == r {
283
+				// Overwrite the route we're removing with the last element
284
+				n.sinfo.StaticRoutes[i] = n.sinfo.StaticRoutes[lastIndex]
285
+				// Shorten the slice to trim the extra element
286
+				n.sinfo.StaticRoutes = n.sinfo.StaticRoutes[:lastIndex]
287
+				break
288
+			}
289
+		}
290
+		n.Unlock()
291
+	}
292
+	return err
293
+}
294
+
266 295
 func (n *networkNamespace) Interfaces() []*Interface {
267 296
 	n.Lock()
268 297
 	defer n.Unlock()
... ...
@@ -35,6 +35,12 @@ type Sandbox interface {
35 35
 	// Set default IPv6 gateway for the sandbox
36 36
 	SetGatewayIPv6(gw net.IP) error
37 37
 
38
+	// Add a static route to the sandbox.
39
+	AddStaticRoute(*types.StaticRoute) error
40
+
41
+	// Remove a static route from the sandbox.
42
+	RemoveStaticRoute(*types.StaticRoute) error
43
+
38 44
 	// Destroy the sandbox
39 45
 	Destroy() error
40 46
 }
... ...
@@ -51,7 +57,11 @@ type Info struct {
51 51
 	// IPv6 gateway for the sandbox.
52 52
 	GatewayIPv6 net.IP
53 53
 
54
-	// TODO: Add routes and ip tables etc.
54
+	// Additional static routes for the sandbox.  (Note that directly
55
+	// connected routes are stored on the particular interface they refer to.)
56
+	StaticRoutes []*types.StaticRoute
57
+
58
+	// TODO: Add ip tables etc.
55 59
 }
56 60
 
57 61
 // Interface represents the settings and identity of a network device. It is
... ...
@@ -74,15 +84,25 @@ type Interface struct {
74 74
 
75 75
 	// IPv6 address for the interface.
76 76
 	AddressIPv6 *net.IPNet
77
+
78
+	// IP routes for the interface.
79
+	Routes []*net.IPNet
77 80
 }
78 81
 
79 82
 // GetCopy returns a copy of this Interface structure
80 83
 func (i *Interface) GetCopy() *Interface {
84
+	copiedRoutes := make([]*net.IPNet, len(i.Routes))
85
+
86
+	for index := range i.Routes {
87
+		copiedRoutes[index] = types.GetIPNetCopy(i.Routes[index])
88
+	}
89
+
81 90
 	return &Interface{
82 91
 		SrcName:     i.SrcName,
83 92
 		DstName:     i.DstName,
84 93
 		Address:     types.GetIPNetCopy(i.Address),
85 94
 		AddressIPv6: types.GetIPNetCopy(i.AddressIPv6),
95
+		Routes:      copiedRoutes,
86 96
 	}
87 97
 }
88 98
 
... ...
@@ -108,6 +128,16 @@ func (i *Interface) Equal(o *Interface) bool {
108 108
 		return false
109 109
 	}
110 110
 
111
+	if len(i.Routes) != len(o.Routes) {
112
+		return false
113
+	}
114
+
115
+	for index := range i.Routes {
116
+		if !types.CompareIPNet(i.Routes[index], o.Routes[index]) {
117
+			return false
118
+		}
119
+	}
120
+
111 121
 	return true
112 122
 }
113 123
 
... ...
@@ -120,7 +150,15 @@ func (s *Info) GetCopy() *Info {
120 120
 	gw := types.GetIPCopy(s.Gateway)
121 121
 	gw6 := types.GetIPCopy(s.GatewayIPv6)
122 122
 
123
-	return &Info{Interfaces: list, Gateway: gw, GatewayIPv6: gw6}
123
+	routes := make([]*types.StaticRoute, len(s.StaticRoutes))
124
+	for i, r := range s.StaticRoutes {
125
+		routes[i] = r.GetCopy()
126
+	}
127
+
128
+	return &Info{Interfaces: list,
129
+		Gateway:      gw,
130
+		GatewayIPv6:  gw6,
131
+		StaticRoutes: routes}
124 132
 }
125 133
 
126 134
 // Equal checks if this instance of SandboxInfo is equal to the passed one
... ...
@@ -154,6 +192,17 @@ func (s *Info) Equal(o *Info) bool {
154 154
 		}
155 155
 	}
156 156
 
157
+	for index := range s.StaticRoutes {
158
+		ss := s.StaticRoutes[index]
159
+		oo := o.StaticRoutes[index]
160
+		if !types.CompareIPNet(ss.Destination, oo.Destination) {
161
+			return false
162
+		}
163
+		if !ss.NextHop.Equal(oo.NextHop) {
164
+			return false
165
+		}
166
+	}
167
+
157 168
 	return true
158 169
 
159 170
 }
... ...
@@ -63,6 +63,13 @@ func newInfo(t *testing.T) (*Info, error) {
63 63
 	intf1.AddressIPv6 = addrv6
64 64
 	intf1.AddressIPv6.IP = ip6
65 65
 
66
+	_, route, err := net.ParseCIDR("192.168.2.1/32")
67
+	if err != nil {
68
+		return nil, err
69
+	}
70
+
71
+	intf1.Routes = []*net.IPNet{route}
72
+
66 73
 	veth = &netlink.Veth{
67 74
 		LinkAttrs: netlink.LinkAttrs{Name: vethName3, TxQLen: 0},
68 75
 		PeerName:  vethName4}
... ...
@@ -92,6 +99,7 @@ func newInfo(t *testing.T) (*Info, error) {
92 92
 	intf2.AddressIPv6.IP = ip6
93 93
 
94 94
 	sinfo := &Info{Interfaces: []*Interface{intf1, intf2}}
95
+
95 96
 	sinfo.Gateway = net.ParseIP("192.168.1.1")
96 97
 	// sinfo.GatewayIPv6 = net.ParseIP("2001:DB8::1")
97 98
 	sinfo.GatewayIPv6 = net.ParseIP("fe80::1")
... ...
@@ -190,12 +190,14 @@ func getInterfaceList() []*Interface {
190 190
 			DstName:     "eth0",
191 191
 			Address:     netv4a,
192 192
 			AddressIPv6: netv6a,
193
+			Routes:      []*net.IPNet{netv4a, netv6a},
193 194
 		},
194 195
 		&Interface{
195 196
 			SrcName:     "veth7654321",
196 197
 			DstName:     "eth1",
197 198
 			Address:     netv4b,
198 199
 			AddressIPv6: netv6b,
200
+			Routes:      []*net.IPNet{netv4b, netv6b},
199 201
 		},
200 202
 	}
201 203
 }
... ...
@@ -184,6 +184,40 @@ func CompareIPNet(a, b *net.IPNet) bool {
184 184
 	return a.IP.Equal(b.IP) && bytes.Equal(a.Mask, b.Mask)
185 185
 }
186 186
 
187
+const (
188
+	// NEXTHOP indicates a StaticRoute with an IP next hop.
189
+	NEXTHOP = iota
190
+
191
+	// CONNECTED indicates a StaticRoute with a interface for directly connected peers.
192
+	CONNECTED
193
+)
194
+
195
+// StaticRoute is a statically-provisioned IP route.
196
+type StaticRoute struct {
197
+	Destination *net.IPNet
198
+
199
+	RouteType int // NEXT_HOP or CONNECTED
200
+
201
+	// NextHop will be resolved by the kernel (i.e. as a loose hop).
202
+	NextHop net.IP
203
+
204
+	// InterfaceID must refer to a defined interface on the
205
+	// Endpoint to which the routes are specified.  Routes specified this way
206
+	// are interpreted as directly connected to the specified interface (no
207
+	// next hop will be used).
208
+	InterfaceID int
209
+}
210
+
211
+// GetCopy returns a copy of this StaticRoute structure
212
+func (r *StaticRoute) GetCopy() *StaticRoute {
213
+	d := GetIPNetCopy(r.Destination)
214
+	nh := GetIPCopy(r.NextHop)
215
+	return &StaticRoute{Destination: d,
216
+		RouteType:   r.RouteType,
217
+		NextHop:     nh,
218
+		InterfaceID: r.InterfaceID}
219
+}
220
+
187 221
 /******************************
188 222
  * Well-known Error Interfaces
189 223
  ******************************/