From 59adcc9b1e21e2cb8cb8fc4a9310012f38a0915d Mon Sep 17 00:00:00 2001 From: Jing Chen Date: Fri, 28 Jun 2024 16:59:01 -0700 Subject: [PATCH] Implement IFLA_IFNAME which specifies an interface's name. IFLA_IFNAME is used to set an interface's name when being used with RTM_NEWLINK and RTM_SETLINK. RTM_NEWLINK is the preferred way, it is backwards compatible with RTM_SETLINK. For gVisor, when RTM_SETLINK is introduced, it shall just share the implementation. PiperOrigin-RevId: 647833657 --- pkg/sentry/socket/netstack/stack.go | 20 +++---- pkg/tcpip/stack/stack.go | 13 +++++ test/rtnetlink/linux/veth_test.sh | 11 ++++ test/syscalls/linux/socket_netlink_route.cc | 58 +++++++++++++++++++++ 4 files changed, 93 insertions(+), 9 deletions(-) diff --git a/pkg/sentry/socket/netstack/stack.go b/pkg/sentry/socket/netstack/stack.go index a0781bd21..49599e210 100644 --- a/pkg/sentry/socket/netstack/stack.go +++ b/pkg/sentry/socket/netstack/stack.go @@ -123,15 +123,13 @@ func (s *Stack) SetInterface(ctx context.Context, msg *nlmsg.Message) *syserr.Er if len(value) < 1 { return syserr.ErrInvalidArgument } - if ifinfomsg.Index != 0 { - // Device name changing isn't supported yet. - return syserr.ErrNotSupported - } - ifname = value.String() - for idx, ifa := range s.Interfaces() { - if ifname == ifa.Name { - ifinfomsg.Index = idx - break + if ifinfomsg.Index == 0 { + ifname = value.String() + for idx, ifa := range s.Interfaces() { + if ifname == ifa.Name { + ifinfomsg.Index = idx + break + } } } case linux.IFLA_MASTER: @@ -190,6 +188,10 @@ func (s *Stack) setLink(id tcpip.NICID, linkAttrs map[uint16]nlmsg.BytesView) *s if err := s.Stack.SetNICAddress(id, addr); err != nil { return syserr.TranslateNetstackError(err) } + case linux.IFLA_IFNAME: + if err := s.Stack.SetNICName(id, v.String()); err != nil { + return syserr.TranslateNetstackError(err) + } } } return nil diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index 8b4711a81..9f5e4c9ad 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -1057,6 +1057,19 @@ func (s *Stack) SetNICAddress(id tcpip.NICID, addr tcpip.LinkAddress) tcpip.Erro return nil } +// SetNICName sets a NIC's name. +func (s *Stack) SetNICName(id tcpip.NICID, name string) tcpip.Error { + s.mu.Lock() + defer s.mu.Unlock() + + nic, ok := s.nics[id] + if !ok { + return &tcpip.ErrUnknownNICID{} + } + nic.name = name + return nil +} + // NICInfo captures the name and addresses assigned to a NIC. type NICInfo struct { Name string diff --git a/test/rtnetlink/linux/veth_test.sh b/test/rtnetlink/linux/veth_test.sh index d1c10fffa..4e24d3f02 100755 --- a/test/rtnetlink/linux/veth_test.sh +++ b/test/rtnetlink/linux/veth_test.sh @@ -30,6 +30,17 @@ if ! wait_for ! ip link show test_veth02; then exit 1 fi +# Create a new veth pair in the current namespace and rename the link. +ip link add name test_veth01 type veth peer name test_veth02 +ip link set test_veth01 name test_veth03 +ip link show test_veth03 +ip link del name test_veth03 +# Check that test_veth02 has been destroyed. +if ! wait_for ! ip link show test_veth02; then + fail "test_veth02 hasn't been destroyed" + exit 1 +fi + ip netns add test ip link add test_veth01 type veth peer name test_veth02 netns test ip link show test_veth01 diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc index 337249fd4..dac631741 100644 --- a/test/syscalls/linux/socket_netlink_route.cc +++ b/test/syscalls/linux/socket_netlink_route.cc @@ -200,6 +200,64 @@ TEST(NetlinkRouteTest, GetLinkByIndex) { EXPECT_TRUE(found) << "Netlink response does not contain any links."; } +TEST(NetlinkRouteTest, ChangeLinkName) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); + SKIP_IF(IsRunningWithHostinet()); + // Hosts that run with old kernel allow renaming only when + // the interface is down. The restriction has been removed. + SKIP_IF(!IsRunningOnGvisor()); + 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 rtattr rtattr; + char ifname[IFNAMSIZ]; + char pad[NLMSG_ALIGNTO + RTA_ALIGNTO]; + }; + + const std::string new_linkname = "notloopback"; + + // Change the link name. + 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.rtattr.rta_type = IFLA_IFNAME; + req.rtattr.rta_len = RTA_LENGTH(new_linkname.size() + 1); + strncpy(req.ifname, new_linkname.c_str(), sizeof(req.ifname)); + req.hdr.nlmsg_len = + NLMSG_LENGTH(sizeof(req.ifm)) + NLMSG_ALIGN(req.rtattr.rta_len); + EXPECT_NO_ERRNO(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req))); + + // Search the link by the new name. + loopback_link.name = new_linkname; + req.hdr.nlmsg_type = RTM_GETLINK; + req.ifm.ifi_index = 0; + req.hdr.nlmsg_flags = NLM_F_REQUEST; + req.rtattr.rta_type = IFLA_IFNAME; + req.rtattr.rta_len = RTA_LENGTH(new_linkname.size() + 1); + strncpy(req.ifname, new_linkname.c_str(), sizeof(req.ifname)); + req.hdr.nlmsg_len = + NLMSG_LENGTH(sizeof(req.ifm)) + NLMSG_ALIGN(req.rtattr.rta_len); + + bool found = false; + ASSERT_NO_ERRNO(NetlinkRequestResponse( + fd, &req, sizeof(req), + [&](const struct nlmsghdr* hdr) { + CheckLinkMsg(hdr, loopback_link); + found = true; + }, + false)); + 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());