diff --git a/pkg/sentry/socket/netstack/stack.go b/pkg/sentry/socket/netstack/stack.go index e0c20dd13..639b1b125 100644 --- a/pkg/sentry/socket/netstack/stack.go +++ b/pkg/sentry/socket/netstack/stack.go @@ -206,10 +206,10 @@ func (s *Stack) setLink(ctx context.Context, id tcpip.NICID, linkAttrs map[uint1 } } case linux.IFLA_ADDRESS: - addr, err := tcpip.ParseMACAddress(v.String()) - if err != nil { + if len(v) != tcpip.LinkAddressSize { return syserr.ErrInvalidArgument } + addr := tcpip.LinkAddress(v) if err := s.Stack.SetNICAddress(id, addr); err != nil { return syserr.TranslateNetstackError(err) } diff --git a/pkg/tcpip/link/loopback/loopback.go b/pkg/tcpip/link/loopback/loopback.go index 72329998b..ffab1033a 100644 --- a/pkg/tcpip/link/loopback/loopback.go +++ b/pkg/tcpip/link/loopback/loopback.go @@ -38,6 +38,8 @@ type endpoint struct { // +checklocks:mu dispatcher stack.NetworkDispatcher // +checklocks:mu + addr tcpip.LinkAddress + // +checklocks:mu mtu uint32 } @@ -91,12 +93,18 @@ func (*endpoint) MaxHeaderLength() uint16 { } // LinkAddress returns the link address of this endpoint. -func (*endpoint) LinkAddress() tcpip.LinkAddress { - return "" +func (e *endpoint) LinkAddress() tcpip.LinkAddress { + e.mu.RLock() + defer e.mu.RUnlock() + return e.addr } // SetLinkAddress implements stack.LinkEndpoint.SetLinkAddress. -func (*endpoint) SetLinkAddress(tcpip.LinkAddress) {} +func (e *endpoint) SetLinkAddress(addr tcpip.LinkAddress) { + e.mu.Lock() + defer e.mu.Unlock() + e.addr = addr +} // Wait implements stack.LinkEndpoint.Wait. func (*endpoint) Wait() {} diff --git a/pkg/tcpip/tcpip.go b/pkg/tcpip/tcpip.go index 33c7ddab4..29c421e86 100644 --- a/pkg/tcpip/tcpip.go +++ b/pkg/tcpip/tcpip.go @@ -55,6 +55,11 @@ const ( ipv6ProtocolNumber = 0x86dd ) +const ( + // LinkAddressSize is the size of a MAC address. + LinkAddressSize = 6 +) + // Errors related to Subnet var ( errSubnetLengthMismatch = errors.New("subnet length of address and mask differ") @@ -2705,7 +2710,7 @@ func ParseMACAddress(s string) (LinkAddress, error) { parts := strings.FieldsFunc(s, func(c rune) bool { return c == ':' || c == '-' }) - if len(parts) != 6 { + if len(parts) != LinkAddressSize { return "", fmt.Errorf("inconsistent parts: %s", s) } addr := make([]byte, 0, len(parts)) @@ -2721,7 +2726,7 @@ func ParseMACAddress(s string) (LinkAddress, error) { // GetRandMacAddr returns a mac address that can be used for local virtual devices. func GetRandMacAddr() LinkAddress { - mac := make(net.HardwareAddr, 6) + mac := make(net.HardwareAddr, LinkAddressSize) rand.Read(mac) // Fill with random data. mac[0] &^= 0x1 // Clear multicast bit. mac[0] |= 0x2 // Set local assignment bit (IEEE802). diff --git a/test/rtnetlink/linux/setlink_test.sh b/test/rtnetlink/linux/setlink_test.sh index 2931fc87d..6744b5974 100755 --- a/test/rtnetlink/linux/setlink_test.sh +++ b/test/rtnetlink/linux/setlink_test.sh @@ -38,3 +38,14 @@ if ! wait_for ! ip link show test_veth02; then fail "test_veth02 hasn't been destroyed" exit 1 fi + +# Change the hardware address of a new veth device. +ip link add name test_veth01 type veth peer name test_veth02 +ip link set dev test_veth01 address 1a:2a:3a:4a:5a:6a +ip link show test_veth01 | grep "1a:2a:3a:4a:5a:6a" +ip link del name test_veth01 +# 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 diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc index 14b860d72..12139577d 100644 --- a/test/syscalls/linux/socket_netlink_route.cc +++ b/test/syscalls/linux/socket_netlink_route.cc @@ -172,9 +172,14 @@ void CheckLinkMsg(const struct nlmsghdr* hdr, const Link& link) { EXPECT_NE(nullptr, rta_mtu) << "IFLA_MTU not found in message."; if (rta_mtu != nullptr) { const auto mtu = *(uint32_t*)(RTA_DATA(rta_mtu)); - std::cout << "mtu: " << mtu << "link.mtu " << link.mtu << std::endl; EXPECT_EQ(mtu, link.mtu); } + const struct rtattr* rta_address = FindRtAttr(hdr, msg, IFLA_ADDRESS); + EXPECT_NE(nullptr, rta_address) << "IFLA_ADDRESS not found in message."; + if (rta_address != nullptr) { + std::string address(reinterpret_cast(RTA_DATA(rta_address))); + EXPECT_EQ(address, link.address); + } } TEST(NetlinkRouteTest, GetLinkByIndex) { @@ -327,6 +332,69 @@ TEST_P(NetlinkSetLinkTest, ChangeMTU) { EXPECT_TRUE(found) << "Netlink response does not contain any links."; } +TEST_P(NetlinkSetLinkTest, ChangeMACAddress) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); + SKIP_IF(!IsRunningOnGvisor()); + 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 rtattr rtattr; + char address[1024]; + }; + + const int address_size = 6; + const char address[address_size + 1] = {static_cast(0xa1), + static_cast(0xa2), + static_cast(0xa3), + static_cast(0xa4), + static_cast(0xa5), + static_cast(0xa6), + '\0'}; + + // Change the link MAC address. + struct request req = {}; + req.hdr.nlmsg_type = GetParam(); + 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_ADDRESS; + req.rtattr.rta_len = RTA_LENGTH(address_size); + strncpy(req.address, address, sizeof(req.address)); + req.hdr.nlmsg_len = + NLMSG_LENGTH(sizeof(req.ifm)) + NLMSG_ALIGN(req.rtattr.rta_len); + EXPECT_NO_ERRNO(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req))); + + loopback_link.address = std::string(address); + // Search the link by its index. + struct searchrequest { + struct nlmsghdr hdr; + struct ifinfomsg ifm; + } search = {}; + search.hdr.nlmsg_len = sizeof(search); + search.hdr.nlmsg_type = RTM_GETLINK; + search.hdr.nlmsg_flags = NLM_F_REQUEST; + search.hdr.nlmsg_seq = kSeq; + search.ifm.ifi_family = AF_UNSPEC; + search.ifm.ifi_index = loopback_link.index; + + bool found = false; + ASSERT_NO_ERRNO(NetlinkRequestResponse( + fd, &search, sizeof(search), + [&](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()); diff --git a/test/syscalls/linux/socket_netlink_route_util.cc b/test/syscalls/linux/socket_netlink_route_util.cc index 5289e83d9..61faeb50e 100644 --- a/test/syscalls/linux/socket_netlink_route_util.cc +++ b/test/syscalls/linux/socket_netlink_route_util.cc @@ -285,6 +285,11 @@ PosixErrorOr> DumpLinks() { links.back().mtu = rta_mtu == nullptr ? 0 : *reinterpret_cast(RTA_DATA(rta_mtu)); + const auto* rta_address = FindRtAttr(hdr, msg, IFLA_ADDRESS); + links.back().address = + rta_address == nullptr + ? "" + : std::string(reinterpret_cast(RTA_DATA(rta_address))); })); return links; } diff --git a/test/syscalls/linux/socket_netlink_route_util.h b/test/syscalls/linux/socket_netlink_route_util.h index a8f5e8efd..fdb1bb5a0 100644 --- a/test/syscalls/linux/socket_netlink_route_util.h +++ b/test/syscalls/linux/socket_netlink_route_util.h @@ -30,6 +30,7 @@ struct Link { int16_t type; std::string name; uint32_t mtu; + std::string address; }; PosixError DumpLinks(const FileDescriptor& fd, uint32_t seq,