Implement RTM_DELROUTE in netstack.

PiperOrigin-RevId: 675711612
This commit is contained in:
Jing Chen
2024-09-17 14:33:41 -07:00
committed by gVisor bot
parent fe969be4cf
commit f681bcc095
9 changed files with 220 additions and 35 deletions
+3
View File
@@ -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
+5
View File
@@ -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
+5
View File
@@ -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() {}
@@ -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.ErrNoNet
}
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
@@ -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:
+59 -27
View File
@@ -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{
@@ -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) {
+8 -4
View File
@@ -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
+9 -4
View File
@@ -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
})
@@ -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) {
+12
View File
@@ -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
+105
View File
@@ -1195,6 +1195,111 @@ TEST_P(NetlinkRouteIpInvariantTest, NewRoute) {
EXPECT_TRUE(routeDstFound);
}
TEST_P(NetlinkRouteIpInvariantTest, DeleteRoute) {
// CAP_NET_ADMIN is required to modify the routing table.
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
SKIP_IF(!IsRunningOnGvisor());
SKIP_IF(IsRunningWithHostinet());
// Routes are not savable.
DisableSave ds;
const std::string dst_v4_address = "192.0.3.0";
const std::string dst_v6_address = "2011:db8::";
// Based on the test parameter, build an IPv4 or IPv6 destination subnet.
int family = GetParam();
void* dst = nullptr;
int dst_len;
int prefixlen;
struct in_addr dst_v4;
struct in6_addr dst_v6;
switch (family) {
case AF_INET:
ASSERT_EQ(inet_pton(family, dst_v4_address.c_str(), &dst_v4), 1);
prefixlen = 24;
dst = &dst_v4;
dst_len = sizeof(dst_v4);
break;
case AF_INET6:
ASSERT_EQ(inet_pton(family, dst_v6_address.c_str(), &dst_v6), 1);
prefixlen = 64;
dst = &dst_v6;
dst_len = sizeof(dst_v6);
break;
default:
FAIL() << "address family must be AF_INET or AF_INET6";
}
Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());
ASSERT_NO_ERRNO(
AddUnicastRoute(loopback_link.index, family, prefixlen, dst, dst_len));
ASSERT_NO_ERRNO(
DelUnicastRoute(loopback_link.index, family, prefixlen, dst, dst_len));
FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
struct request {
struct nlmsghdr hdr;
struct rtmsg rtm;
};
struct request req = {};
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETROUTE;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.hdr.nlmsg_seq = kSeq;
req.rtm.rtm_family = AF_UNSPEC;
bool routeDstFound = false;
ASSERT_NO_ERRNO(NetlinkRequestResponse(
fd, &req, sizeof(req),
[&](const struct nlmsghdr* hdr) {
// Validate the reponse to RTM_GETROUTE.
EXPECT_THAT(hdr->nlmsg_type, AnyOf(Eq(RTM_NEWROUTE), Eq(NLMSG_DONE)));
// The test should not proceed if it's not a RTM_NEWROUTE message.
if (hdr->nlmsg_type != RTM_NEWROUTE) {
return;
}
const struct rtmsg* msg =
reinterpret_cast<const struct rtmsg*>(NLMSG_DATA(hdr));
int len = RTM_PAYLOAD(hdr);
for (struct rtattr* attr = RTM_RTA(msg); RTA_OK(attr, len);
attr = RTA_NEXT(attr, len)) {
if (attr->rta_type == RTA_DST) {
char v4_address[INET_ADDRSTRLEN] = {};
char v6_address[INET6_ADDRSTRLEN] = {};
switch (family) {
case AF_INET:
inet_ntop(AF_INET, RTA_DATA(attr), v4_address,
sizeof(v4_address));
if (strcmp(v4_address, dst_v4_address.c_str()) == 0) {
routeDstFound = true;
return;
}
break;
case AF_INET6:
inet_ntop(AF_INET6, RTA_DATA(attr), v6_address,
sizeof(v6_address));
if (strcmp(v6_address, dst_v6_address.c_str()) == 0) {
routeDstFound = true;
return;
}
break;
}
}
}
},
false));
// No route that matches the given destination address can be found.
EXPECT_FALSE(routeDstFound);
// Removing a route that doens't exist returns an error.
EXPECT_THAT(
DelUnicastRoute(loopback_link.index, family, prefixlen, dst, dst_len),
PosixErrorIs(ESRCH, _));
}
TEST_P(NetlinkRouteIpInvariantTest, AddAndRemoveRoute) {
// Gvisor does not support `RTM_NEWROUTE` or `RTM_DELROUTE`.
SKIP_IF(IsRunningOnGvisor() && GvisorPlatform() != Platform::kStarnix);