Skip to content

Commit 65acaaf

Browse files
tomdeepaultiplady
authored andcommitted
Allow drivers to supply static routes for interfaces
Signed-off-by: Tom Denham <tom.denham@metaswitch.com>
1 parent db7178a commit 65acaaf

File tree

11 files changed

+274
-2
lines changed

11 files changed

+274
-2
lines changed

libnetwork/driverapi/driverapi.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ type JoinInfo interface {
104104
// SetGatewayIPv6 sets the default IPv6 gateway when a container joins the endpoint.
105105
SetGatewayIPv6(net.IP) error
106106

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+
107111
// SetHostsPath sets the overriding /etc/hosts path to use for the container.
108112
SetHostsPath(string) error
109113

libnetwork/drivers/bridge/bridge_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ type testEndpoint struct {
8787
gw6 net.IP
8888
hostsPath string
8989
resolvConfPath string
90+
routes []types.StaticRoute
9091
}
9192

9293
func (te *testEndpoint) Interfaces() []driverapi.InterfaceInfo {
@@ -157,6 +158,11 @@ func (te *testEndpoint) SetResolvConfPath(path string) error {
157158
return nil
158159
}
159160

161+
func (te *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error {
162+
te.routes = append(te.routes, types.StaticRoute{destination, routeType, nextHop, interfaceID})
163+
return nil
164+
}
165+
160166
func TestQueryEndpointInfo(t *testing.T) {
161167
testQueryEndpointInfo(t, true)
162168
}

libnetwork/drivers/remote/driver_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ func (test *testEndpoint) SetNames(src string, dst string) error {
149149
return nil
150150
}
151151

152+
func (test *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error {
153+
//TODO
154+
return nil
155+
}
156+
152157
func (test *testEndpoint) ID() int {
153158
return test.id
154159
}

libnetwork/endpoint.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ func (ep *endpoint) Join(containerID string, options ...EndpointOption) (*Contai
293293
SrcName: i.srcName,
294294
DstName: i.dstPrefix,
295295
Address: &i.addr,
296+
Routes: i.routes,
296297
}
297298
if i.addrv6.IP.To16() != nil {
298299
iface.AddressIPv6 = &i.addrv6
@@ -302,6 +303,13 @@ func (ep *endpoint) Join(containerID string, options ...EndpointOption) (*Contai
302303
return nil, err
303304
}
304305
}
306+
// Set up non-interface routes.
307+
for _, r := range ep.joinInfo.StaticRoutes {
308+
err = sb.AddStaticRoute(r)
309+
if err != nil {
310+
return nil, err
311+
}
312+
}
305313

306314
err = sb.SetGateway(joinInfo.gw)
307315
if err != nil {
@@ -360,6 +368,14 @@ func (ep *endpoint) Leave(containerID string, options ...EndpointOption) error {
360368
}
361369
}
362370

371+
// Remove non-interface routes.
372+
for _, r := range ep.joinInfo.StaticRoutes {
373+
err = sb.RemoveStaticRoute(r)
374+
if err != nil {
375+
logrus.Debugf("Remove route failed: %v", err)
376+
}
377+
}
378+
363379
ctrlr.sandboxRm(container.data.SandboxKey)
364380

365381
return err

libnetwork/endpoint_info.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,15 @@ type endpointInterface struct {
4646
addrv6 net.IPNet
4747
srcName string
4848
dstPrefix string
49+
routes []*net.IPNet
4950
}
5051

5152
type endpointJoinInfo struct {
5253
gw net.IP
5354
gw6 net.IP
5455
hostsPath string
5556
resolvConfPath string
57+
StaticRoutes []*types.StaticRoute
5658
}
5759

5860
func (ep *endpoint) Info() EndpointInfo {
@@ -149,6 +151,35 @@ func (ep *endpoint) InterfaceNames() []driverapi.InterfaceNameInfo {
149151
return iList
150152
}
151153

154+
func (ep *endpoint) AddStaticRoute(destination *net.IPNet, routeType int, nextHop net.IP, interfaceID int) error {
155+
ep.Lock()
156+
defer ep.Unlock()
157+
158+
r := types.StaticRoute{destination, routeType, nextHop, interfaceID}
159+
160+
if routeType == types.NEXTHOP {
161+
// If the route specifies a next-hop, then it's loosely routed (i.e. not bound to a particular interface).
162+
ep.joinInfo.StaticRoutes = append(ep.joinInfo.StaticRoutes, &r)
163+
} else {
164+
// If the route doesn't specify a next-hop, it must be a connected route, bound to an interface.
165+
if err := ep.addInterfaceRoute(&r); err != nil {
166+
return err
167+
}
168+
}
169+
return nil
170+
}
171+
172+
func (ep *endpoint) addInterfaceRoute(route *types.StaticRoute) error {
173+
for _, iface := range ep.iFaces {
174+
if iface.id == route.InterfaceID {
175+
iface.routes = append(iface.routes, route.Destination)
176+
return nil
177+
}
178+
}
179+
return types.BadRequestErrorf("Interface with ID %d doesn't exist.",
180+
route.InterfaceID)
181+
}
182+
152183
func (ep *endpoint) SandboxKey() string {
153184
ep.Lock()
154185
defer ep.Unlock()

libnetwork/sandbox/configure_linux.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ func configureInterface(iface netlink.Link, settings *Interface) error {
1919
{setInterfaceName, fmt.Sprintf("error renaming interface %q to %q", ifaceName, settings.DstName)},
2020
{setInterfaceIP, fmt.Sprintf("error setting interface %q IP to %q", ifaceName, settings.Address)},
2121
{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)},
2223
}
2324

2425
for _, config := range ifaceConfigurators {
@@ -63,6 +64,78 @@ func programGateway(path string, gw net.IP) error {
6364
})
6465
}
6566

67+
// Program a route in to the namespace routing table.
68+
func programRoute(path string, dest *net.IPNet, nh net.IP) error {
69+
runtime.LockOSThread()
70+
defer runtime.UnlockOSThread()
71+
72+
origns, err := netns.Get()
73+
if err != nil {
74+
return err
75+
}
76+
defer origns.Close()
77+
78+
f, err := os.OpenFile(path, os.O_RDONLY, 0)
79+
if err != nil {
80+
return fmt.Errorf("failed get network namespace %q: %v", path, err)
81+
}
82+
defer f.Close()
83+
84+
nsFD := f.Fd()
85+
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
86+
return err
87+
}
88+
defer netns.Set(origns)
89+
90+
gwRoutes, err := netlink.RouteGet(nh)
91+
if err != nil {
92+
return fmt.Errorf("route for the next hop could not be found: %v", err)
93+
}
94+
95+
return netlink.RouteAdd(&netlink.Route{
96+
Scope: netlink.SCOPE_UNIVERSE,
97+
LinkIndex: gwRoutes[0].LinkIndex,
98+
Gw: gwRoutes[0].Gw,
99+
Dst: dest,
100+
})
101+
}
102+
103+
// Delete a route from the namespace routing table.
104+
func removeRoute(path string, dest *net.IPNet, nh net.IP) error {
105+
runtime.LockOSThread()
106+
defer runtime.UnlockOSThread()
107+
108+
origns, err := netns.Get()
109+
if err != nil {
110+
return err
111+
}
112+
defer origns.Close()
113+
114+
f, err := os.OpenFile(path, os.O_RDONLY, 0)
115+
if err != nil {
116+
return fmt.Errorf("failed get network namespace %q: %v", path, err)
117+
}
118+
defer f.Close()
119+
120+
nsFD := f.Fd()
121+
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
122+
return err
123+
}
124+
defer netns.Set(origns)
125+
126+
gwRoutes, err := netlink.RouteGet(nh)
127+
if err != nil {
128+
return fmt.Errorf("route for the next hop could not be found: %v", err)
129+
}
130+
131+
return netlink.RouteDel(&netlink.Route{
132+
Scope: netlink.SCOPE_UNIVERSE,
133+
LinkIndex: gwRoutes[0].LinkIndex,
134+
Gw: gwRoutes[0].Gw,
135+
Dst: dest,
136+
})
137+
}
138+
66139
func setInterfaceIP(iface netlink.Link, settings *Interface) error {
67140
ipAddr := &netlink.Addr{IPNet: settings.Address, Label: ""}
68141
return netlink.AddrAdd(iface, ipAddr)
@@ -79,3 +152,17 @@ func setInterfaceIPv6(iface netlink.Link, settings *Interface) error {
79152
func setInterfaceName(iface netlink.Link, settings *Interface) error {
80153
return netlink.LinkSetName(iface, settings.DstName)
81154
}
155+
156+
func setInterfaceRoutes(iface netlink.Link, settings *Interface) error {
157+
for _, route := range settings.Routes {
158+
err := netlink.RouteAdd(&netlink.Route{
159+
Scope: netlink.SCOPE_UNIVERSE,
160+
LinkIndex: iface.Attrs().Index,
161+
Dst: route,
162+
})
163+
if err != nil {
164+
return err
165+
}
166+
}
167+
return nil
168+
}

libnetwork/sandbox/namespace_linux.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"sync"
99
"syscall"
1010

11+
"github.com/docker/libnetwork/types"
1112
"github.com/vishvananda/netlink"
1213
"github.com/vishvananda/netns"
1314
)
@@ -263,6 +264,35 @@ func (n *networkNamespace) SetGatewayIPv6(gw net.IP) error {
263264
return err
264265
}
265266

267+
func (n *networkNamespace) AddStaticRoute(r *types.StaticRoute) error {
268+
err := programRoute(n.path, r.Destination, r.NextHop)
269+
if err == nil {
270+
n.Lock()
271+
n.sinfo.StaticRoutes = append(n.sinfo.StaticRoutes, r)
272+
n.Unlock()
273+
}
274+
return err
275+
}
276+
277+
func (n *networkNamespace) RemoveStaticRoute(r *types.StaticRoute) error {
278+
err := removeRoute(n.path, r.Destination, r.NextHop)
279+
if err == nil {
280+
n.Lock()
281+
lastIndex := len(n.sinfo.StaticRoutes) - 1
282+
for i, v := range n.sinfo.StaticRoutes {
283+
if v == r {
284+
// Overwrite the route we're removing with the last element
285+
n.sinfo.StaticRoutes[i] = n.sinfo.StaticRoutes[lastIndex]
286+
// Shorten the slice to trim the extra element
287+
n.sinfo.StaticRoutes = n.sinfo.StaticRoutes[:lastIndex]
288+
break
289+
}
290+
}
291+
n.Unlock()
292+
}
293+
return err
294+
}
295+
266296
func (n *networkNamespace) Interfaces() []*Interface {
267297
n.Lock()
268298
defer n.Unlock()

libnetwork/sandbox/sandbox.go

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ type Sandbox interface {
3535
// Set default IPv6 gateway for the sandbox
3636
SetGatewayIPv6(gw net.IP) error
3737

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+
3844
// Destroy the sandbox
3945
Destroy() error
4046
}
@@ -51,7 +57,11 @@ type Info struct {
5157
// IPv6 gateway for the sandbox.
5258
GatewayIPv6 net.IP
5359

54-
// TODO: Add routes and ip tables etc.
60+
// Additional static routes for the sandbox. (Note that directly
61+
// connected routes are stored on the particular interface they refer to.)
62+
StaticRoutes []*types.StaticRoute
63+
64+
// TODO: Add ip tables etc.
5565
}
5666

5767
// Interface represents the settings and identity of a network device. It is
@@ -74,15 +84,25 @@ type Interface struct {
7484

7585
// IPv6 address for the interface.
7686
AddressIPv6 *net.IPNet
87+
88+
// IP routes for the interface.
89+
Routes []*net.IPNet
7790
}
7891

7992
// GetCopy returns a copy of this Interface structure
8093
func (i *Interface) GetCopy() *Interface {
94+
copiedRoutes := make([]*net.IPNet, len(i.Routes))
95+
96+
for index := range i.Routes {
97+
copiedRoutes[index] = types.GetIPNetCopy(i.Routes[index])
98+
}
99+
81100
return &Interface{
82101
SrcName: i.SrcName,
83102
DstName: i.DstName,
84103
Address: types.GetIPNetCopy(i.Address),
85104
AddressIPv6: types.GetIPNetCopy(i.AddressIPv6),
105+
Routes: copiedRoutes,
86106
}
87107
}
88108

@@ -108,6 +128,16 @@ func (i *Interface) Equal(o *Interface) bool {
108128
return false
109129
}
110130

131+
if len(i.Routes) != len(o.Routes) {
132+
return false
133+
}
134+
135+
for index := range i.Routes {
136+
if !types.CompareIPNet(i.Routes[index], o.Routes[index]) {
137+
return false
138+
}
139+
}
140+
111141
return true
112142
}
113143

@@ -120,7 +150,15 @@ func (s *Info) GetCopy() *Info {
120150
gw := types.GetIPCopy(s.Gateway)
121151
gw6 := types.GetIPCopy(s.GatewayIPv6)
122152

123-
return &Info{Interfaces: list, Gateway: gw, GatewayIPv6: gw6}
153+
routes := make([]*types.StaticRoute, len(s.StaticRoutes))
154+
for i, r := range s.StaticRoutes {
155+
routes[i] = r.GetCopy()
156+
}
157+
158+
return &Info{Interfaces: list,
159+
Gateway: gw,
160+
GatewayIPv6: gw6,
161+
StaticRoutes: routes}
124162
}
125163

126164
// Equal checks if this instance of SandboxInfo is equal to the passed one
@@ -154,6 +192,17 @@ func (s *Info) Equal(o *Info) bool {
154192
}
155193
}
156194

195+
for index := range s.StaticRoutes {
196+
ss := s.StaticRoutes[index]
197+
oo := o.StaticRoutes[index]
198+
if !types.CompareIPNet(ss.Destination, oo.Destination) {
199+
return false
200+
}
201+
if !ss.NextHop.Equal(oo.NextHop) {
202+
return false
203+
}
204+
}
205+
157206
return true
158207

159208
}

0 commit comments

Comments
 (0)