diff --git a/pkg/sentry/socket/netlink/route/protocol.go b/pkg/sentry/socket/netlink/route/protocol.go index e01ff03154..3b8fd1a4e1 100644 --- a/pkg/sentry/socket/netlink/route/protocol.go +++ b/pkg/sentry/socket/netlink/route/protocol.go @@ -161,6 +161,72 @@ func (p *Protocol) getLink(ctx context.Context, msg *netlink.Message, ms *netlin return nil } +func (p *Protocol) newLink(ctx context.Context, msg *netlink.Message, ms *netlink.MessageSet) *syserr.Error { + stack := inet.StackFromContext(ctx) + if stack == nil { + // No network stack. + return syserr.ErrProtocolNotSupported + } + + var ifinfomsg linux.InterfaceInfoMessage + attrs, ok := msg.GetData(&ifinfomsg) + if !ok { + return syserr.ErrInvalidArgument + } + for !attrs.Empty() { + // The index is unspecified, search by the interface name. + ahdr, value, rest, ok := attrs.ParseFirst() + if !ok { + return syserr.ErrInvalidArgument + } + attrs = rest + switch ahdr.Type { + case linux.IFLA_IFNAME: + if len(value) < 1 { + return syserr.ErrInvalidArgument + } + if ifinfomsg.Index != 0 { + // Device name changing isn't supported yet. + return syserr.ErrNotSupported + } + ifname := string(value[:len(value)-1]) + for idx, ifa := range stack.Interfaces() { + if ifname == ifa.Name { + ifinfomsg.Index = idx + break + } + } + default: + ctx.Warningf("unexpected attribute: %x", ahdr.Type) + return syserr.ErrNotSupported + } + } + if ifinfomsg.Index == 0 { + return syserr.ErrNoDevice + } + + flags := msg.Header().Flags + if flags&linux.NLM_F_EXCL != 0 { + return syserr.ErrExists + } + if flags&linux.NLM_F_REPLACE != 0 { + return syserr.ErrExists + } + + if ifinfomsg.Flags != 0 || ifinfomsg.Change != 0 { + if ifinfomsg.Change & ^uint32(linux.IFF_UP) != 0 { + ctx.Warningf("Unsupported ifi_change flags: %x", ifinfomsg.Change) + return syserr.ErrInvalidArgument + } + if ifinfomsg.Flags & ^uint32(linux.IFF_UP) != 0 { + ctx.Warningf("Unsupported ifi_flags: %x", ifinfomsg.Change) + return syserr.ErrInvalidArgument + } + // Netstack interfaces are always up. + } + return nil +} + // delLink handles RTM_DELLINK requests. func (p *Protocol) delLink(ctx context.Context, msg *netlink.Message, ms *netlink.MessageSet) *syserr.Error { stack := inet.StackFromContext(ctx) @@ -576,6 +642,8 @@ func (p *Protocol) ProcessMessage(ctx context.Context, msg *netlink.Message, ms } } else if hdr.Flags&linux.NLM_F_REQUEST == linux.NLM_F_REQUEST { switch hdr.Type { + case linux.RTM_NEWLINK: + return p.newLink(ctx, msg, ms) case linux.RTM_GETLINK: return p.getLink(ctx, msg, ms) case linux.RTM_DELLINK: diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc index 695d70c3ff..4bd48d7a0c 100644 --- a/test/syscalls/linux/socket_netlink_route.cc +++ b/test/syscalls/linux/socket_netlink_route.cc @@ -198,6 +198,32 @@ TEST(NetlinkRouteTest, GetLinkByIndex) { EXPECT_TRUE(found) << "Netlink response does not contain any links."; } +TEST(NetlinkRouteTest, LinkUp) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); + SKIP_IF(IsRunningWithHostinet()); + + Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); + + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); + + struct request { + struct nlmsghdr hdr; + struct ifinfomsg ifm; + }; + + struct request req = {}; + req.hdr.nlmsg_len = sizeof(req); + req.hdr.nlmsg_type = RTM_NEWLINK; + req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.hdr.nlmsg_seq = kSeq; + req.ifm.ifi_family = AF_UNSPEC; + req.ifm.ifi_index = loopback_link.index; + req.ifm.ifi_change = IFF_UP; + req.ifm.ifi_flags = IFF_UP; + EXPECT_NO_ERRNO(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req))); +} + TEST(NetlinkRouteTest, GetLinkByName) { Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());