Skip to content

Commit

Permalink
Implement RTM_DELROUTE in netstack.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 668338827
  • Loading branch information
milantracy authored and gvisor-bot committed Aug 29, 2024
1 parent 2511e2e commit 2f95b6f
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 35 deletions.
3 changes: 3 additions & 0 deletions pkg/sentry/inet/inet.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ type Stack interface {
// RouteTable returns the network stack's route table.
RouteTable() []Route

// RemoveRoute deletes the specified route.
RemoveRoute(ctx context.Context, msg *nlmsg.Message) *syserr.Error

// NewRoute adds the given route to the network stack's route table.
NewRoute(ctx context.Context, msg *nlmsg.Message) *syserr.Error

Expand Down
5 changes: 5 additions & 0 deletions pkg/sentry/inet/test_stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ func (s *TestStack) RouteTable() []Route {
return s.RouteList
}

// RemoveRoute implements Stack.
func (s *TestStack) RemoveRoute(ctx context.Context, msg *nlmsg.Message) *syserr.Error {
return nil
}

// NewRoute implements Stack.
func (s *TestStack) NewRoute(ctx context.Context, msg *nlmsg.Message) *syserr.Error {
return syserr.ErrNotPermitted
Expand Down
5 changes: 5 additions & 0 deletions pkg/sentry/socket/hostinet/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,11 @@ func (*Stack) NewRoute(context.Context, *nlmsg.Message) *syserr.Error {
return syserr.ErrNotSupported
}

// RemoveRoute implements inet.Stack.RemoveRoute.
func (*Stack) RemoveRoute(context.Context, *nlmsg.Message) *syserr.Error {
return syserr.ErrNotSupported
}

// Pause implements inet.Stack.Pause.
func (*Stack) Pause() {}

Expand Down
14 changes: 14 additions & 0 deletions pkg/sentry/socket/netlink/route/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,18 @@ func (p *Protocol) newRoute(ctx context.Context, s *netlink.Socket, msg *nlmsg.M
return stack.NewRoute(ctx, msg)
}

// deleteRoute handles RTM_DELROUTE requests.
func (p *Protocol) deleteRoute(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
stack := s.Stack()
if stack == nil {
return syserr.ErrProtocolNotSupported
}
if msg.Header().Flags&linux.NLM_F_REQUEST != linux.NLM_F_REQUEST {
return syserr.ErrProtocolNotSupported
}
return stack.RemoveRoute(ctx, msg)
}

// dumpRoutes handles RTM_GETROUTE requests.
func (p *Protocol) dumpRoutes(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
// RTM_GETROUTE dump requests need not contain anything more than the
Expand Down Expand Up @@ -635,6 +647,8 @@ func (p *Protocol) ProcessMessage(ctx context.Context, s *netlink.Socket, msg *n
return p.newRoute(ctx, s, msg, ms)
case linux.RTM_GETROUTE:
return p.dumpRoutes(ctx, s, msg, ms)
case linux.RTM_DELROUTE:
return p.deleteRoute(ctx, s, msg, ms)
case linux.RTM_NEWADDR:
return p.newAddr(ctx, s, msg, ms)
case linux.RTM_DELADDR:
Expand Down
86 changes: 59 additions & 27 deletions pkg/sentry/socket/netstack/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,87 +749,86 @@ func (s *Stack) RouteTable() []inet.Route {
return routeTable
}

// NewRoute implements inet.Stack.NewRoute.
func (s *Stack) NewRoute(ctx context.Context, msg *nlmsg.Message) *syserr.Error {
var routeMsg linux.RouteMessage
attrs, ok := msg.GetData(&routeMsg)
// localRoute constructs a local route from the netlink message.
func (s *Stack) localRoute(msg *nlmsg.Message) (tcpip.Route, *syserr.Error) {
var rtMsg linux.RouteMessage
attrs, ok := msg.GetData(&rtMsg)
if !ok {
return syserr.ErrInvalidArgument
return tcpip.Route{}, syserr.ErrInvalidArgument
}

route := inet.Route{
Family: routeMsg.Family,
DstLen: routeMsg.DstLen,
SrcLen: routeMsg.SrcLen,
TOS: routeMsg.TOS,
Table: routeMsg.Table,
Protocol: routeMsg.Protocol,
Scope: routeMsg.Scope,
Type: routeMsg.Type,
Flags: routeMsg.Flags,
Family: rtMsg.Family,
DstLen: rtMsg.DstLen,
SrcLen: rtMsg.SrcLen,
TOS: rtMsg.TOS,
Table: rtMsg.Table,
Protocol: rtMsg.Protocol,
Scope: rtMsg.Scope,
Type: rtMsg.Type,
Flags: rtMsg.Flags,
}

for !attrs.Empty() {
ahdr, value, rest, ok := attrs.ParseFirst()
if !ok {
return syserr.ErrInvalidArgument
return tcpip.Route{}, syserr.ErrInvalidArgument
}
attrs = rest

switch ahdr.Type {
case linux.RTA_DST:
if len(value) < 1 {
return syserr.ErrInvalidArgument
return tcpip.Route{}, syserr.ErrInvalidArgument
}
route.DstAddr = value
case linux.RTA_SRC:
if len(value) < 1 {
return syserr.ErrInvalidArgument
return tcpip.Route{}, syserr.ErrInvalidArgument
}
route.SrcAddr = value
case linux.RTA_OIF:
oif := nlmsg.BytesView(value)
outputInterface, ok := oif.Int32()
if !ok {
return syserr.ErrInvalidArgument
return tcpip.Route{}, syserr.ErrInvalidArgument
}
if _, exist := s.Interfaces()[outputInterface]; !exist {
return syserr.ErrNoDevice
return tcpip.Route{}, syserr.ErrNoDevice
}
route.OutputInterface = outputInterface
case linux.RTA_GATEWAY:
if len(value) < 1 {
return syserr.ErrInvalidArgument
return tcpip.Route{}, syserr.ErrInvalidArgument
}
route.GatewayAddr = value
case linux.RTA_PRIORITY:
default:
ctx.Warningf("Unknown attribute: %v", ahdr.Type)
return syserr.ErrNotSupported
log.Warningf("Unknown attribute: %v", ahdr.Type)
return tcpip.Route{}, syserr.ErrNotSupported
}
}

var dest tcpip.Subnet
// When no destination address is provided, the new route might be the default route.
if route.DstAddr == nil {
if route.GatewayAddr == nil {
return syserr.ErrInvalidArgument
return tcpip.Route{}, syserr.ErrInvalidArgument
}
switch len(route.GatewayAddr) {
case header.IPv4AddressSize:
subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice(tcpip.IPv4Zero), tcpip.MaskFromBytes(tcpip.IPv4Zero))
if err != nil {
return syserr.ErrInvalidArgument
return tcpip.Route{}, syserr.ErrInvalidArgument
}
dest = subnet
case header.IPv6AddressSize:
subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice(tcpip.IPv6Zero), tcpip.MaskFromBytes(tcpip.IPv6Zero))
if err != nil {
return syserr.ErrInvalidArgument
return tcpip.Route{}, syserr.ErrInvalidArgument
}
dest = subnet
default:
return syserr.ErrInvalidArgument
return tcpip.Route{}, syserr.ErrInvalidArgument
}
} else {
dest = tcpip.AddressWithPrefix{
Expand All @@ -842,9 +841,42 @@ func (s *Stack) NewRoute(ctx context.Context, msg *nlmsg.Message) *syserr.Error
Gateway: tcpip.AddrFromSlice(route.GatewayAddr),
NIC: tcpip.NICID(route.OutputInterface),
}

if len(route.SrcAddr) != 0 {
localRoute.SourceHint = tcpip.AddrFromSlice(route.SrcAddr)
}

return localRoute, nil
}

// RemoveRoute implements inte.Stack.RemoveRoute.
func (s *Stack) RemoveRoute(ctx context.Context, msg *nlmsg.Message) *syserr.Error {
localRoute, err := s.localRoute(msg)
if err != nil {
return err
}
if removed := s.Stack.RemoveRoutes(func(rt tcpip.Route) bool {
// Both gateway and NIC are compared with existing routes
// only when they are present in the netlink message.
if localRoute.Gateway.Len() > 0 && !localRoute.Gateway.Equal(rt.Gateway) {
return false
}
if localRoute.NIC > 0 && localRoute.NIC != rt.NIC {
return false
}
return rt.Destination.Equal(localRoute.Destination)
}); removed == 0 {
return syserr.ErrNoProcess
}
return nil
}

// NewRoute implements inet.Stack.NewRoute.
func (s *Stack) NewRoute(ctx context.Context, msg *nlmsg.Message) *syserr.Error {
localRoute, err := s.localRoute(msg)
if err != nil {
return err
}
found := false
for _, rt := range s.Stack.GetRouteTable() {
if localRoute.Equal(rt) {
Expand Down
12 changes: 8 additions & 4 deletions pkg/tcpip/stack/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -779,23 +779,27 @@ func (s *Stack) addRouteLocked(route *tcpip.Route) {
s.routeTable.PushBack(route)
}

// RemoveRoutes removes matching routes from the route table.
func (s *Stack) RemoveRoutes(match func(tcpip.Route) bool) {
// RemoveRoutes removes matching routes from the route table, it
// returns the number of routes that are removed.
func (s *Stack) RemoveRoutes(match func(tcpip.Route) bool) int {
s.routeMu.Lock()
defer s.routeMu.Unlock()

s.removeRoutesLocked(match)
return s.removeRoutesLocked(match)
}

// +checklocks:s.routeMu
func (s *Stack) removeRoutesLocked(match func(tcpip.Route) bool) {
func (s *Stack) removeRoutesLocked(match func(tcpip.Route) bool) int {
count := 0
for route := s.routeTable.Front(); route != nil; {
next := route.Next()
if match(*route) {
s.routeTable.Remove(route)
count++
}
route = next
}
return count
}

// ReplaceRoute replaces the route in the routing table which matchse
Expand Down
13 changes: 9 additions & 4 deletions pkg/tcpip/stack/stack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4449,15 +4449,16 @@ func TestRemoveRoutes(t *testing.T) {
t.Fatal(err)
}

// Initialize the route table with three routes.
s.SetRouteTable([]tcpip.Route{
routeList := []tcpip.Route{
{Destination: subnet1, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1},
{Destination: subnet2, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1},
{Destination: subnet3, Gateway: tcpip.AddrFromSlice([]byte("\x00\x00\x00\x00")), NIC: 1},
})
}
// Initialize the route table with three routes.
s.SetRouteTable(routeList)

// Remove routes with the specific address.
s.RemoveRoutes(func(r tcpip.Route) bool {
removed := s.RemoveRoutes(func(r tcpip.Route) bool {
return r.Destination.ID() == addressToRemove
})

Expand All @@ -4471,6 +4472,10 @@ func TestRemoveRoutes(t *testing.T) {
t.Fatalf("Unexpected route got = %#v, want = %#v", got, want)
}
}

if got, want := removed, len(routeList)-len(expected); want != removed {
t.Fatalf("stack.RemoveRoutes(_) removes %v routes, want = %v", got, want)
}
}

func TestFindRouteWithForwarding(t *testing.T) {
Expand Down
12 changes: 12 additions & 0 deletions test/rtnetlink/linux/route_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,23 @@ ip link add name veth1 type veth peer name eth0 netns test
ip netns exec test ip link set up dev lo
ip netns exec test ip link set up dev eth0
ip netns exec test ip addr add 192.168.11.2/24 dev eth0
ORIGINAL_ROUTES=$(ip netns exec test ip r)
ip netns exec test ip r add default via 192.168.11.1 dev eth0
ip netns exec test ip r list | grep "default via 192.168.11.1 dev eth0"
ip netns exec test ip r add 192.168.146.48/28 dev eth0
ip netns exec test ip r list | grep "192.168.146.48/28 dev eth0"
ip netns exec test ip route

# Replace the routes.
ip netns exec test ip r replace default via 192.168.11.2 dev eth0
ip netns exec test ip r list | grep "default via 192.168.11.2 dev eth0"

# Remove all routes that are add/modified above.
ip netns exec test ip r del default via 192.168.11.2 dev eth0
ip netns exec test ip r del 192.168.146.48/28
CURRENT_ROUTES=$(ip netns exec test ip r)

if [[ "$ORIGINAL_ROUTES" != "$CURRENT_ROUTES" ]]; then
fail "unexpected routes are present"
exit 1
fi
Loading

0 comments on commit 2f95b6f

Please sign in to comment.