Skip to content

Commit 0c0df8a

Browse files
authored
Merge pull request kata-containers#148 from egernst/update-routes
grpc: network: add UpdateRoutes
2 parents 71288bb + d41d4e3 commit 0c0df8a

File tree

6 files changed

+497
-444
lines changed

6 files changed

+497
-444
lines changed

grpc.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -728,16 +728,8 @@ func (a *agentGRPC) RemoveInterface(ctx context.Context, req *pb.RemoveInterface
728728
return a.sandbox.removeInterface(nil, req.Interface)
729729
}
730730

731-
func (a *agentGRPC) AddRoute(ctx context.Context, req *pb.AddRouteRequest) (*gpb.Empty, error) {
732-
return emptyResp, a.sandbox.addRoute(nil, req.Route)
733-
}
734-
735-
func (a *agentGRPC) UpdateRoute(ctx context.Context, req *pb.UpdateRouteRequest) (*gpb.Empty, error) {
736-
return emptyResp, nil
737-
}
738-
739-
func (a *agentGRPC) RemoveRoute(ctx context.Context, req *pb.RemoveRouteRequest) (*gpb.Empty, error) {
740-
return emptyResp, a.sandbox.removeRoute(nil, req.Route)
731+
func (a *agentGRPC) UpdateRoutes(ctx context.Context, req *pb.UpdateRoutesRequest) (*pb.Routes, error) {
732+
return a.sandbox.updateRoutes(nil, req.Routes)
741733
}
742734

743735
func (a *agentGRPC) OnlineCPUMem(ctx context.Context, req *pb.OnlineCPUMemRequest) (*gpb.Empty, error) {

network.go

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import (
1717
"github.com/vishvananda/netlink"
1818
)
1919

20+
const (
21+
emptyRouteAddr = "<nil>"
22+
)
23+
2024
// Network fully describes a sandbox network with its interfaces, routes and dns
2125
// related information.
2226
type network struct {
@@ -273,8 +277,130 @@ func getInterface(netHandle *netlink.Handle, link netlink.Link) *pb.Interface {
273277
// Routes //
274278
////////////
275279

276-
func (s *sandbox) addRoute(netHandle *netlink.Handle, route *pb.Route) error {
277-
return s.updateRoute(netHandle, route, true)
280+
//updateRoutes will take requestedRoutes and create netlink routes, with a goal of creating a final
281+
// state which matches the requested routes. In doing this, preesxisting non-loopback routes will be
282+
// removed from the network. If an error occurs, this function returns the list of routes in
283+
// gRPC-route format at the time of failure
284+
func (s *sandbox) updateRoutes(netHandle *netlink.Handle, requestedRoutes *pb.Routes) (resultingRoutes *pb.Routes, err error) {
285+
286+
if netHandle == nil {
287+
netHandle, err = netlink.NewHandle()
288+
if err != nil {
289+
return nil, err
290+
}
291+
defer netHandle.Delete()
292+
}
293+
294+
//If we are returning an error, return the current routes on the system
295+
defer func() {
296+
if err != nil {
297+
resultingRoutes, _ = getCurrentRoutes(netHandle)
298+
}
299+
}()
300+
301+
//
302+
// First things first, let's blow away all the existing routes. The updateRoutes function
303+
// is designed to be declarative, so we will attempt to create state matching what is
304+
// requested, and in the event that we fail to do so, will return the error and final state.
305+
//
306+
307+
initRouteList, err := netHandle.RouteList(nil, netlink.FAMILY_ALL)
308+
if err != nil {
309+
return nil, err
310+
}
311+
for _, initRoute := range initRouteList {
312+
// don't delete routes associated with lo:
313+
link, _ := netHandle.LinkByIndex(initRoute.LinkIndex)
314+
if link.Attrs().Name == "lo" || link.Attrs().Name == "::1" {
315+
continue
316+
}
317+
318+
err = netHandle.RouteDel(&initRoute)
319+
if err != nil {
320+
//If there was an error deleting some of the initial routes,
321+
//return the error and the current routes on the system via
322+
//the defer function
323+
return
324+
}
325+
}
326+
327+
//
328+
// Set each of the requested routes
329+
//
330+
// First make sure we set the interfaces initial routes, as otherwise we
331+
// won't be able to access the gateway
332+
for _, reqRoute := range requestedRoutes.Routes {
333+
if reqRoute.Gateway == "" {
334+
err = s.updateRoute(netHandle, reqRoute, true)
335+
if err != nil {
336+
agentLog.WithError(err).Error("update Route failed")
337+
//If there was an error setting the route, return the error
338+
//and the current routes on the system via the defer func
339+
return
340+
}
341+
342+
}
343+
}
344+
// Take a second pass and apply the routes which include a gateway
345+
for _, reqRoute := range requestedRoutes.Routes {
346+
if reqRoute.Gateway != "" {
347+
err = s.updateRoute(netHandle, reqRoute, true)
348+
if err != nil {
349+
agentLog.WithError(err).Error("update Route failed")
350+
//If there was an error setting the route, return the
351+
//error and the current routes on the system via defer
352+
return
353+
}
354+
}
355+
}
356+
357+
return requestedRoutes, err
358+
}
359+
360+
//getCurrentRoutes is a helper to gather existing routes in gRPC protocol format
361+
func getCurrentRoutes(netHandle *netlink.Handle) (*pb.Routes, error) {
362+
363+
if netHandle == nil {
364+
netHandle, err := netlink.NewHandle()
365+
if err != nil {
366+
return nil, err
367+
}
368+
defer netHandle.Delete()
369+
}
370+
371+
var routes pb.Routes
372+
373+
finalRouteList, err := netHandle.RouteList(nil, netlink.FAMILY_ALL)
374+
if err != nil {
375+
return &routes, err
376+
}
377+
378+
for _, route := range finalRouteList {
379+
var r pb.Route
380+
if route.Dst != nil {
381+
r.Dest = route.Dst.String()
382+
}
383+
384+
if route.Gw != nil {
385+
r.Gateway = route.Gw.String()
386+
}
387+
388+
if route.Src != nil {
389+
r.Source = route.Src.String()
390+
}
391+
392+
r.Scope = uint32(route.Scope)
393+
394+
link, err := netHandle.LinkByIndex(route.LinkIndex)
395+
if err != nil {
396+
return &routes, err
397+
}
398+
r.Device = link.Attrs().Name
399+
400+
routes.Routes = append(routes.Routes, &r)
401+
}
402+
403+
return &routes, nil
278404
}
279405

280406
func (s *sandbox) removeRoute(netHandle *netlink.Handle, route *pb.Route) error {
@@ -321,7 +447,9 @@ func (s *sandbox) updateRoute(netHandle *netlink.Handle, route *pb.Route, add bo
321447
netRoute := &netlink.Route{
322448
LinkIndex: linkAttrs.Index,
323449
Dst: dst,
450+
Src: net.ParseIP(route.Source),
324451
Gw: net.ParseIP(route.Gateway),
452+
Scope: netlink.Scope(route.Scope),
325453
}
326454

327455
if add {

network_test.go

Lines changed: 97 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (c) 2017 Intel Corporation
2+
// Copyright (c) 2018 Intel Corporation
33
//
44
// SPDX-License-Identifier: Apache-2.0
55
//
@@ -8,12 +8,15 @@ package main
88

99
import (
1010
"net"
11+
"os"
1112
"reflect"
13+
"runtime"
1214
"testing"
1315

1416
pb "github.com/kata-containers/agent/protocols/grpc"
1517
"github.com/stretchr/testify/assert"
1618
"github.com/vishvananda/netlink"
19+
"github.com/vishvananda/netns"
1720
)
1821

1922
func TestUpdateRemoveInterface(t *testing.T) {
@@ -74,7 +77,6 @@ func TestUpdateRemoveInterface(t *testing.T) {
7477
"Interface created didn't match: got %+v, expecting %+v", resultingIfc, ifc)
7578

7679
// Try with garbage:
77-
//
7880
ifc.Mtu = 999999999999
7981
resultingIfc, err = s.updateInterface(netHandle, &ifc)
8082
// expecting this failed
@@ -84,30 +86,104 @@ func TestUpdateRemoveInterface(t *testing.T) {
8486
assert.True(t, reflect.DeepEqual(resultingIfc, &ifc),
8587
"Resulting inteface should have been unchanged: got %+v, expecting %+v", resultingIfc, ifc)
8688

87-
//
88-
// Test adding routes:
89-
//
90-
route := pb.Route{
91-
Dest: "192.168.3.0/24",
92-
Gateway: "192.168.0.1",
93-
Device: "enoNumber",
94-
}
95-
err = s.addRoute(netHandle, &route)
96-
assert.Nil(t, err, "add route failed: %v", err)
97-
98-
//
99-
// Test remove routes:
100-
//
101-
err = s.removeRoute(netHandle, &route)
102-
assert.Nil(t, err, "remove route failed: %v", err)
103-
104-
//
10589
// Exercise the removeInterface code:
106-
//
10790
_, err = s.removeInterface(netHandle, &ifc)
10891
assert.Nil(t, err, "remove interface failed: %v", err)
10992

11093
// Try to remove non existent interface:
11194
_, err = s.removeInterface(netHandle, &ifc)
11295
assert.NotNil(t, err, "Expected failed removal: %v", err)
11396
}
97+
98+
type teardownNetworkTest func()
99+
100+
func skipUnlessRoot(t *testing.T) {
101+
if os.Getuid() != 0 {
102+
t.Skip("")
103+
}
104+
}
105+
106+
func setupNetworkTest(t *testing.T) teardownNetworkTest {
107+
skipUnlessRoot(t)
108+
109+
// new temporary namespace so we don't pollute the host
110+
// lock thread since the namespace is thread local
111+
runtime.LockOSThread()
112+
var err error
113+
ns, err := netns.New()
114+
if err != nil {
115+
t.Fatal("Failed to create newns", ns)
116+
}
117+
118+
return func() {
119+
ns.Close()
120+
runtime.UnlockOSThread()
121+
}
122+
}
123+
124+
func TestUpdateRoutes(t *testing.T) {
125+
tearDown := setupNetworkTest(t)
126+
defer tearDown()
127+
128+
s := sandbox{}
129+
130+
// create a dummy link which we'll play with
131+
macAddr := net.HardwareAddr{0x02, 0x00, 0xCA, 0xFE, 0x00, 0x48}
132+
link := &netlink.Dummy{
133+
LinkAttrs: netlink.LinkAttrs{
134+
MTU: 1500,
135+
TxQLen: -1,
136+
Name: "ifc-name",
137+
HardwareAddr: macAddr,
138+
},
139+
}
140+
netHandle, _ := netlink.NewHandle()
141+
defer netHandle.Delete()
142+
143+
netHandle.LinkAdd(link)
144+
if err := netHandle.LinkSetUp(link); err != nil {
145+
t.Fatal(err)
146+
}
147+
netlinkAddr, _ := netlink.ParseAddr("192.168.0.2/16")
148+
netHandle.AddrAdd(link, netlinkAddr)
149+
150+
//Test a simple route setup:
151+
inputRoutesSimple := []*pb.Route{
152+
{Dest: "", Gateway: "192.168.0.1", Source: "", Scope: 0, Device: "ifc-name"},
153+
{Dest: "192.168.0.0/16", Gateway: "", Source: "192.168.0.2", Scope: 253, Device: "ifc-name"},
154+
}
155+
156+
testRoutes := &pb.Routes{
157+
Routes: inputRoutesSimple,
158+
}
159+
160+
results, err := s.updateRoutes(netHandle, testRoutes)
161+
assert.Nil(t, err, "Unexpected update interface failure: %v", err)
162+
assert.True(t, reflect.DeepEqual(results, testRoutes),
163+
"Interface created didn't match: got %+v, expecting %+v", results, testRoutes)
164+
165+
//Test a route setup mimicking what could be provided by PTP CNI plugin:
166+
inputRoutesPTPExample := []*pb.Route{
167+
{Dest: "", Gateway: "192.168.0.1", Source: "", Scope: 0, Device: "ifc-name"},
168+
{Dest: "192.168.0.144/16", Gateway: "192.168.0.1", Source: "192.168.0.2", Scope: 0, Device: "ifc-name"},
169+
{Dest: "192.168.0.1/32", Gateway: "", Source: "192.168.0.2", Scope: 254, Device: "ifc-name"},
170+
}
171+
testRoutes.Routes = inputRoutesPTPExample
172+
173+
results, err = s.updateRoutes(netHandle, testRoutes)
174+
assert.Nil(t, err, "Unexpected update interface failure: %v", err)
175+
assert.True(t, reflect.DeepEqual(results, testRoutes),
176+
"Interface created didn't match: got %+v, expecting %+v", results, testRoutes)
177+
178+
//Test unreachable example (no scope provided for initial link route)
179+
inputRoutesNoScope := []*pb.Route{
180+
{Dest: "", Gateway: "192.168.0.1", Source: "", Scope: 0, Device: "ifc-name"},
181+
{Dest: "192.168.0.0/16", Gateway: "", Source: "192.168.0.2", Scope: 0, Device: "ifc-name"},
182+
}
183+
testRoutes.Routes = inputRoutesNoScope
184+
results, err = s.updateRoutes(netHandle, testRoutes)
185+
assert.NotNil(t, err, "Expected to observe unreachable route failure")
186+
187+
assert.True(t, reflect.DeepEqual(results.Routes[0], testRoutes.Routes[1]),
188+
"Interface created didn't match: got %+v, expecting %+v", results.Routes[0], testRoutes.Routes[1])
189+
}

0 commit comments

Comments
 (0)