From 83a4caf2a702e822fba2b331e5482daced3cd1b8 Mon Sep 17 00:00:00 2001 From: Lucas Manning Date: Wed, 5 Feb 2025 11:35:29 -0800 Subject: [PATCH] Implement MappablePacketEndpoint for PACKET_MMAP and add tests. PiperOrigin-RevId: 723590936 --- pkg/abi/linux/socket.go | 5 +- pkg/sentry/socket/netstack/BUILD | 2 + pkg/sentry/socket/netstack/netstack.go | 59 ++- pkg/tcpip/transport/packet/endpoint.go | 85 ++++- test/syscalls/BUILD | 5 + test/syscalls/linux/BUILD | 22 ++ test/syscalls/linux/packet_mmap.cc | 481 +++++++++++++++++++++++++ 7 files changed, 641 insertions(+), 18 deletions(-) create mode 100644 test/syscalls/linux/packet_mmap.cc diff --git a/pkg/abi/linux/socket.go b/pkg/abi/linux/socket.go index 2efd17c60..1b6b13afb 100644 --- a/pkg/abi/linux/socket.go +++ b/pkg/abi/linux/socket.go @@ -185,7 +185,8 @@ type TpacketHdr struct { TpMac uint16 TpNet uint16 TpSec uint32 - TpUsec uint32 `marshal:"unaligned"` + TpUsec uint32 + _ [4]byte } // TpacketAlignment is the alignment of a frame in a packet_mmap ring buffer @@ -202,7 +203,7 @@ const ( // TPACKET_HDRLEN is the length of a TpacketHdr from . var ( - TPACKET_HDRLEN = TPacketAlign(uint32((*TpacketHdr)(nil).SizeBytes()) + uint32((*SockAddrLink)(nil).SizeBytes())) + TPACKET_HDRLEN = TPacketAlign(uint32((*TpacketHdr)(nil).SizeBytes())) + uint32((*SockAddrLink)(nil).SizeBytes()) ) // TPacketAlign aligns a value to the alignment of a TPacket. diff --git a/pkg/sentry/socket/netstack/BUILD b/pkg/sentry/socket/netstack/BUILD index f801bb501..cebd8db5b 100644 --- a/pkg/sentry/socket/netstack/BUILD +++ b/pkg/sentry/socket/netstack/BUILD @@ -41,9 +41,11 @@ go_library( "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", "//pkg/sentry/ktime", + "//pkg/sentry/memmap", "//pkg/sentry/socket", "//pkg/sentry/socket/netfilter", "//pkg/sentry/socket/netlink/nlmsg", + "//pkg/sentry/socket/netstack/packetmmap", "//pkg/sentry/vfs", "//pkg/sync", "//pkg/syserr", diff --git a/pkg/sentry/socket/netstack/netstack.go b/pkg/sentry/socket/netstack/netstack.go index 2277b92ab..540b84cf6 100644 --- a/pkg/sentry/socket/netstack/netstack.go +++ b/pkg/sentry/socket/netstack/netstack.go @@ -51,9 +51,11 @@ import ( "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/ktime" + "gvisor.dev/gvisor/pkg/sentry/memmap" "gvisor.dev/gvisor/pkg/sentry/socket" "gvisor.dev/gvisor/pkg/sentry/socket/netfilter" epb "gvisor.dev/gvisor/pkg/sentry/socket/netstack/events_go_proto" + "gvisor.dev/gvisor/pkg/sentry/socket/netstack/packetmmap" "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/sync" "gvisor.dev/gvisor/pkg/syserr" @@ -1868,10 +1870,7 @@ func SetSockOpt(t *kernel.Task, s socket.Socket, ep commonEndpoint, level int, n return setSockOptIP(t, s, ep, name, optVal) case linux.SOL_PACKET: - // gVisor doesn't support any SOL_PACKET options just return not - // supported. Returning nil here will result in tcpdump thinking AF_PACKET - // features are supported and proceed to use them and break. - return syserr.ErrProtocolNotAvailable + return setSockOptPacket(t, s, ep, name, optVal) case linux.SOL_UDP, linux.SOL_RAW: @@ -2718,6 +2717,42 @@ func setSockOptIP(t *kernel.Task, s socket.Socket, ep commonEndpoint, name int, return nil } +func setSockOptPacket(t *kernel.Task, s socket.Socket, ep commonEndpoint, name int, optVal []byte) *syserr.Error { + switch name { + case linux.PACKET_RX_RING: + var tpacketReq linux.TpacketReq + tpacketReq.UnmarshalBytes(optVal) + req := tcpip.TpacketReq{ + TpBlockSize: tpacketReq.TpBlockSize, + TpBlockNr: tpacketReq.TpBlockNr, + TpFrameSize: tpacketReq.TpFrameSize, + TpFrameNr: tpacketReq.TpFrameNr, + } + if err := ep.SetSockOpt(&req); err != nil { + return syserr.TranslateNetstackError(err) + } + if ep, ok := ep.(stack.MappablePacketEndpoint); ok { + var pme *packetmmap.Endpoint + if ep.GetPacketMMapEndpoint() != nil { + pme = ep.GetPacketMMapEndpoint().(*packetmmap.Endpoint) + if pme.Mapped() { + return syserr.ErrBusy + } + } else { + pme = &packetmmap.Endpoint{} + } + opts := ep.GetPacketMMapOpts(&req, true /* isRx */) + if err := pme.Init(t, opts); err != nil { + return syserr.FromError(err) + } + ep.SetPacketMMapEndpoint(pme) + } else { + return syserr.ErrNotSupported + } + } + return nil +} + // GetSockName implements the linux syscall getsockname(2) for sockets backed by // tcpip.Endpoint. func (s *sock) GetSockName(*kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { @@ -3543,3 +3578,19 @@ func (s *sock) EventRegister(e *waiter.Entry) error { func (s *sock) EventUnregister(e *waiter.Entry) { s.Queue.EventUnregister(e) } + +// ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap. +func (s *sock) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error { + if mappablePacketEP, ok := s.Endpoint.(stack.MappablePacketEndpoint); ok { + packetMMapEP := mappablePacketEP.GetPacketMMapEndpoint() + if packetMMapEP == nil { + return linuxerr.ENODEV + } + ep := packetMMapEP.(*packetmmap.Endpoint) + if err := vfs.GenericConfigureMMap(&s.vfsfd, ep, opts); err != nil { + return err + } + return ep.ConfigureMMap(ctx, opts) + } + return linuxerr.ENODEV +} diff --git a/pkg/tcpip/transport/packet/endpoint.go b/pkg/tcpip/transport/packet/endpoint.go index 0cf7c0158..10e222d97 100644 --- a/pkg/tcpip/transport/packet/endpoint.go +++ b/pkg/tcpip/transport/packet/endpoint.go @@ -36,6 +36,8 @@ import ( "gvisor.dev/gvisor/pkg/waiter" ) +var _ stack.MappablePacketEndpoint = (*endpoint)(nil) + // +stateify savable type packet struct { packetEntry @@ -91,6 +93,10 @@ type endpoint struct { lastErrorMu sync.Mutex `state:"nosave"` // +checklocks:lastErrorMu lastError tcpip.Error + + packetMmapRxConfig *tcpip.TpacketReq + packetMmapTxConfig *tcpip.TpacketReq + packetMMapEp stack.PacketMMapEndpoint } // NewEndpoint returns a new packet endpoint. @@ -136,6 +142,11 @@ func (ep *endpoint) Close() { ep.stack.UnregisterPacketEndpoint(ep.boundNIC, ep.boundNetProto, ep) + if ep.packetMMapEp != nil { + ep.packetMMapEp.Close() + ep.packetMMapEp = nil + } + ep.rcvMu.Lock() defer ep.rcvMu.Unlock() @@ -348,6 +359,9 @@ func (ep *endpoint) Readiness(mask waiter.EventMask) waiter.EventMask { // Determine whether the endpoint is readable. if (mask & waiter.ReadableEvents) != 0 { + if ep.packetMMapEp != nil { + result |= ep.packetMMapEp.Readiness(mask) + } ep.rcvMu.Lock() if !ep.rcvList.Empty() || ep.rcvClosed { result |= waiter.ReadableEvents @@ -358,13 +372,18 @@ func (ep *endpoint) Readiness(mask waiter.EventMask) waiter.EventMask { return result } -// SetSockOpt implements tcpip.Endpoint.SetSockOpt. Packet sockets cannot be -// used with SetSockOpt, and this function always returns -// *tcpip.ErrNotSupported. +// SetSockOpt implements tcpip.Endpoint.SetSockOpt. func (ep *endpoint) SetSockOpt(opt tcpip.SettableSocketOption) tcpip.Error { switch opt.(type) { case *tcpip.SocketDetachFilterOption: return nil + case *tcpip.TpacketReq: + ep.rcvMu.Lock() + defer ep.rcvMu.Unlock() + if !ep.rcvList.Empty() { + return &tcpip.ErrWouldBlock{} + } + return nil default: return &tcpip.ErrUnknownProtocolOption{} @@ -415,8 +434,26 @@ func (ep *endpoint) GetSockOptInt(opt tcpip.SockOptInt) (int, tcpip.Error) { } } -// HandlePacket implements stack.PacketEndpoint.HandlePacket. +// handlePacket implements stack.PacketEndpoint.HandlePacket func (ep *endpoint) HandlePacket(nicID tcpip.NICID, netProto tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) { + if ep.packetMMapEp != nil { + ep.packetMMapEp.HandlePacket(nicID, netProto, pkt) + return + } + wasEmpty := ep.handlePacketInner(nicID, netProto, pkt) + + ep.stats.PacketsReceived.Increment() + // Notify waiters that there's data to be read. + if wasEmpty { + ep.waiterQueue.Notify(waiter.ReadableEvents) + } +} + +func (ep *endpoint) HandlePacketMMapCopy(nicID tcpip.NICID, netProto tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) { + _ = ep.handlePacketInner(nicID, netProto, pkt) +} + +func (ep *endpoint) handlePacketInner(nicID tcpip.NICID, netProto tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) bool { ep.rcvMu.Lock() // Drop the packet if our buffer is currently full. @@ -424,7 +461,7 @@ func (ep *endpoint) HandlePacket(nicID tcpip.NICID, netProto tcpip.NetworkProtoc ep.rcvMu.Unlock() ep.stack.Stats().DroppedPackets.Increment() ep.stats.ReceiveErrors.ClosedReceiver.Increment() - return + return false } rcvBufSize := ep.ops.GetReceiveBufferSize() @@ -432,7 +469,7 @@ func (ep *endpoint) HandlePacket(nicID tcpip.NICID, netProto tcpip.NetworkProtoc ep.rcvMu.Unlock() ep.stack.Stats().DroppedPackets.Increment() ep.stats.ReceiveErrors.ReceiveBufferOverflow.Increment() - return + return false } wasEmpty := ep.rcvBufSize == 0 @@ -464,13 +501,8 @@ func (ep *endpoint) HandlePacket(nicID tcpip.NICID, netProto tcpip.NetworkProtoc ep.rcvList.PushBack(&rcvdPkt) ep.rcvBufSize += rcvdPkt.data.Size() - ep.rcvMu.Unlock() - ep.stats.PacketsReceived.Increment() - // Notify waiters that there's data to be read. - if wasEmpty { - ep.waiterQueue.Notify(waiter.ReadableEvents) - } + return wasEmpty } // State implements socket.Socket.State. @@ -497,3 +529,32 @@ func (*endpoint) SetOwner(tcpip.PacketOwner) {} func (ep *endpoint) SocketOptions() *tcpip.SocketOptions { return &ep.ops } + +// GetPacketMMapOpts implements stack.MappablePacketEndpoint.GetPacketMMapOpts. +func (ep *endpoint) GetPacketMMapOpts(req *tcpip.TpacketReq, isRx bool) stack.PacketMMapOpts { + ep.mu.Lock() + defer ep.mu.Unlock() + return stack.PacketMMapOpts{ + Req: req, + IsRx: isRx, + Cooked: ep.cooked, + Stack: ep.stack, + Stats: &ep.stats, + Wq: ep.waiterQueue, + NICID: ep.boundNIC, + NetProto: ep.boundNetProto, + PacketEndpoint: ep, + } +} + +// SetPacketMMapEndpoint implements +// stack.MappablePacketEndpoint.SetPacketMMapEndpoint. +func (ep *endpoint) SetPacketMMapEndpoint(m stack.PacketMMapEndpoint) { + ep.packetMMapEp = m +} + +// GetPacketMMapEndpoint implements +// stack.MappablePacketEndpoint.GetPacketMMapEndpoint. +func (ep *endpoint) GetPacketMMapEndpoint() stack.PacketMMapEndpoint { + return ep.packetMMapEp +} diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 7581c5aff..91f3f3d14 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -1277,3 +1277,8 @@ syscall_test( save = False, test = "//test/syscalls/linux:socketopt_test", ) + +syscall_test( + save = False, + test = "//test/syscalls/linux:packet_mmap_test", +) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 0a0dd62f5..e27bef0f6 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1595,6 +1595,28 @@ cc_binary( ], ) +cc_binary( + name = "packet_mmap_test", + testonly = 1, + srcs = ["packet_mmap.cc"], + linkstatic = 1, + malloc = "//test/util:errno_safe_allocator", + deps = select_gtest() + [ + ":ip_socket_test_util", + "//test/util:capability_util", + "//test/util:cleanup", + "//test/util:file_descriptor", + "//test/util:logging", + "//test/util:posix_error", + "//test/util:socket_util", + "//test/util:test_util", + "//test/util:thread_util", + "@com_google_absl//absl/log", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", + ], +) + cc_binary( name = "pty_test", testonly = 1, diff --git a/test/syscalls/linux/packet_mmap.cc b/test/syscalls/linux/packet_mmap.cc new file mode 100644 index 000000000..c6506326f --- /dev/null +++ b/test/syscalls/linux/packet_mmap.cc @@ -0,0 +1,481 @@ +// Copyright 2024 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/util/cleanup.h" +#include "test/util/file_descriptor.h" +#include "test/util/linux_capability_util.h" +#include "test/util/logging.h" +#include "test/util/posix_error.h" +#include "test/util/socket_util.h" +#include "test/util/test_util.h" +#include "test/util/thread_util.h" + +namespace gvisor { +namespace testing { +namespace { + +PosixErrorOr MakePacketMmapRing(int fd, const sockaddr* bind_addr, + int bind_addr_size, tpacket_req* req) { + RETURN_ERROR_IF_SYSCALL_FAIL( + setsockopt(fd, SOL_PACKET, PACKET_RX_RING, req, sizeof(*req))); + RETURN_ERROR_IF_SYSCALL_FAIL(bind(fd, bind_addr, bind_addr_size)); + uint32_t sz = req->tp_block_size * req->tp_block_nr; + return mmap(0, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); +} + +// Tests that setting the RX ring works and fails if constraints are not met. +TEST(PacketMmapTest, SetRXRingFailsBadRequests) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + ASSERT_THAT(socket(AF_PACKET, SOCK_RAW, 0), SyscallFailsWithErrno(EPERM)); + GTEST_SKIP() << "Missing packet socket capability"; + } + FileDescriptor mmap_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_PACKET, SOCK_DGRAM, 0)); + + // tp_block_size must be a multiple of tp_frame_size. + tpacket_req req = { + .tp_block_size = 100, + .tp_block_nr = 1, + .tp_frame_size = 64, + .tp_frame_nr = 1, + }; + ASSERT_THAT(setsockopt(mmap_sock.get(), SOL_PACKET, PACKET_RX_RING, &req, + sizeof(req)), + SyscallFailsWithErrno(EINVAL)); + // tp_frame_size mut be greater than TPACKET_HDR_LENGTH. + req = { + .tp_block_size = 100, + .tp_block_nr = 1, + .tp_frame_size = 10, + .tp_frame_nr = 1, + }; + ASSERT_THAT(setsockopt(mmap_sock.get(), SOL_PACKET, PACKET_RX_RING, &req, + sizeof(req)), + SyscallFailsWithErrno(EINVAL)); + // tp_frame_size must be a multiple of TPACKET_ALIGNMENT. + req = { + .tp_block_size = 200, + .tp_block_nr = 1, + .tp_frame_size = 100, + .tp_frame_nr = 1, + }; + ASSERT_THAT(setsockopt(mmap_sock.get(), SOL_PACKET, PACKET_RX_RING, &req, + sizeof(req)), + SyscallFailsWithErrno(EINVAL)); + // tp_frame_nr must be exactly frames_per_block / tp_block_nr. + req = { + .tp_block_size = 100, + .tp_block_nr = 1, + .tp_frame_size = 100, + .tp_frame_nr = 2, + }; + ASSERT_THAT(setsockopt(mmap_sock.get(), SOL_PACKET, PACKET_RX_RING, &req, + sizeof(req)), + SyscallFailsWithErrno(EINVAL)); + // tp_block_nr must be at least 1. + req = { + .tp_block_size = 100, + .tp_block_nr = 0, + .tp_frame_size = 100, + .tp_frame_nr = 1, + }; + ASSERT_THAT(setsockopt(mmap_sock.get(), SOL_PACKET, PACKET_RX_RING, &req, + sizeof(req)), + SyscallFailsWithErrno(EINVAL)); +} + +TEST(PacketMmapTest, Basic) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + ASSERT_THAT(socket(AF_PACKET, SOCK_RAW, 0), SyscallFailsWithErrno(EPERM)); + GTEST_SKIP() << "Missing packet socket capability"; + } + sockaddr_ll bind_addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IP), + .sll_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()), + .sll_halen = ETH_ALEN, + }; + FileDescriptor mmap_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_PACKET, SOCK_DGRAM, 0)); + + uint32_t tp_frame_size = 65536 + 128; + uint32_t tp_block_size = tp_frame_size * 32; + uint32_t tp_block_nr = 2; + uint32_t tp_frame_nr = (tp_block_size * tp_block_nr) / tp_frame_size; + tpacket_req req = { + .tp_block_size = tp_block_size, + .tp_block_nr = tp_block_nr, + .tp_frame_size = tp_frame_size, + .tp_frame_nr = tp_frame_nr, + }; + void* ring = ASSERT_NO_ERRNO_AND_VALUE(MakePacketMmapRing( + mmap_sock.get(), reinterpret_cast(&bind_addr), + sizeof(bind_addr), &req)); + auto ring_cleanup = Cleanup([ring, tp_block_size, tp_block_nr] { + ASSERT_THAT(munmap(ring, tp_block_size * tp_block_nr), SyscallSucceeds()); + }); + + std::string kMessage = "123abc"; + ASSERT_THAT( + sendto(mmap_sock.get(), kMessage.c_str(), kMessage.size(), 0 /* flags */, + reinterpret_cast(&bind_addr), sizeof(bind_addr)), + SyscallSucceeds()); + + tpacket_hdr* hdr = reinterpret_cast(ring); + struct pollfd pollset; + pollset.fd = mmap_sock.get(); + pollset.revents = 0; + pollset.events = POLLIN | POLLRDNORM | POLLERR; + ASSERT_THAT(poll(&pollset, 1, -1), SyscallSucceeds()); + EXPECT_EQ(hdr->tp_status & TP_STATUS_USER, 1); + EXPECT_EQ(hdr->tp_len, kMessage.size()); + EXPECT_EQ(hdr->tp_snaplen, kMessage.size()); + EXPECT_STREQ((char*)(hdr) + hdr->tp_net, kMessage.c_str()); +} + +TEST(PacketMmapTest, FillBlocks) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + ASSERT_THAT(socket(AF_PACKET, SOCK_RAW, 0), SyscallFailsWithErrno(EPERM)); + GTEST_SKIP() << "Missing packet socket capability "; + } + sockaddr_ll bind_addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IP), + .sll_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()), + .sll_halen = ETH_ALEN, + }; + FileDescriptor mmap_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_PACKET, SOCK_DGRAM, 0)); + + uint32_t tp_frame_size = 65536 + 128; + uint32_t tp_block_size = tp_frame_size * 32; + uint32_t tp_block_nr = 2; + uint32_t tp_frame_nr = (tp_block_size * tp_block_nr) / tp_frame_size; + tpacket_req req = { + .tp_block_size = tp_block_size, + .tp_block_nr = tp_block_nr, + .tp_frame_size = tp_frame_size, + .tp_frame_nr = tp_frame_nr, + }; + void* ring = ASSERT_NO_ERRNO_AND_VALUE(MakePacketMmapRing( + mmap_sock.get(), reinterpret_cast(&bind_addr), + sizeof(bind_addr), &req)); + auto ring_cleanup = Cleanup([ring, tp_block_size, tp_block_nr] { + ASSERT_THAT(munmap(ring, tp_block_size * tp_block_nr), SyscallSucceeds()); + }); + + std::string kMessage = "123abc"; + for (uint32_t i = 0; i < tp_frame_nr; i++) { + ASSERT_THAT( + sendto(mmap_sock.get(), kMessage.c_str(), kMessage.size(), + 0 /* flags */, reinterpret_cast(&bind_addr), + sizeof(bind_addr)), + SyscallSucceeds()); + } + + // This send will wrap around to the first frame, but the ring buffer + // should still be full. + std::string kNewMessage = "HELLO!!!"; + ASSERT_THAT( + sendto(mmap_sock.get(), kNewMessage.c_str(), kNewMessage.size(), + 0 /* flags */, reinterpret_cast(&bind_addr), + sizeof(bind_addr)), + SyscallSucceeds()); + + struct tpacket_hdr* hdr = reinterpret_cast(ring); + struct pollfd pollset; + pollset.fd = mmap_sock.get(); + pollset.revents = 0; + pollset.events = POLLIN | POLLRDNORM | POLLERR; + ASSERT_THAT(poll(&pollset, 1, -1), SyscallSucceeds()); + EXPECT_EQ(hdr->tp_status & TP_STATUS_USER, 1); + // We should not see the new message. It was dropped. + EXPECT_STREQ((char*)(hdr) + hdr->tp_net, kMessage.c_str()); + + // Wait until the last frame has been processed. + hdr = reinterpret_cast( + reinterpret_cast(ring) + ((tp_frame_nr - 1) * tp_frame_size)); + while (!(hdr->tp_status & TP_STATUS_USER)) { + absl::SleepFor(absl::Milliseconds(100)); + } + + // Mark all frames as kernel owned. + for (uint32_t i = 0; i < tp_frame_nr; i++) { + hdr = reinterpret_cast( + (reinterpret_cast(ring) + (i * tp_frame_size))); + ASSERT_EQ(hdr->tp_status & TP_STATUS_USER, 1); + hdr->tp_status = TP_STATUS_KERNEL; + } + + ASSERT_THAT( + sendto(mmap_sock.get(), kNewMessage.data(), kNewMessage.size(), + 0 /* flags */, reinterpret_cast(&bind_addr), + sizeof(bind_addr)), + SyscallSucceeds()); + + hdr = reinterpret_cast(ring); + pollset.fd = mmap_sock.get(); + pollset.revents = 0; + pollset.events = POLLIN | POLLRDNORM | POLLERR; + ASSERT_THAT(poll(&pollset, 1, -1), SyscallSucceeds()); + EXPECT_EQ(hdr->tp_status & TP_STATUS_USER, 1); + // We should now see the new message. + EXPECT_STREQ((char*)(hdr) + hdr->tp_net, kNewMessage.c_str()); +} + +TEST(PacketMmapTest, ConcurrentReadWrite) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + ASSERT_THAT(socket(AF_PACKET, SOCK_RAW, 0), SyscallFailsWithErrno(EPERM)); + GTEST_SKIP() << "Missing packet socket capability"; + } + sockaddr_ll bind_addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IP), + .sll_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()), + .sll_halen = ETH_ALEN, + }; + FileDescriptor mmap_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_PACKET, SOCK_DGRAM, 0)); + + uint32_t tp_frame_size = 65536 + 128; + uint32_t tp_block_size = tp_frame_size * 32; + uint32_t tp_block_nr = 2; + uint32_t tp_frame_nr = (tp_block_size * tp_block_nr) / tp_frame_size; + tpacket_req req = { + .tp_block_size = tp_block_size, + .tp_block_nr = tp_block_nr, + .tp_frame_size = tp_frame_size, + .tp_frame_nr = tp_frame_nr, + }; + void* ring = ASSERT_NO_ERRNO_AND_VALUE(MakePacketMmapRing( + mmap_sock.get(), reinterpret_cast(&bind_addr), + sizeof(bind_addr), &req)); + auto ring_cleanup = Cleanup([ring, tp_block_size, tp_block_nr] { + ASSERT_THAT(munmap(ring, tp_block_size * tp_block_nr), SyscallSucceeds()); + }); + + const std::string kMessage = "123abc"; + + ScopedThread sender([&] { + for (uint32_t i = 0; i < tp_frame_nr; i++) { + ASSERT_THAT( + sendto(mmap_sock.get(), kMessage.c_str(), kMessage.size(), + 0 /* flags */, reinterpret_cast(&bind_addr), + sizeof(bind_addr)), + SyscallSucceeds()); + } + }); + ScopedThread receiver([&] { + struct tpacket_hdr* hdr = reinterpret_cast(ring); + for (uint32_t i = 0; i < tp_frame_nr; i++) { + struct pollfd pollset; + pollset.fd = mmap_sock.get(); + pollset.revents = 0; + pollset.events = POLLIN | POLLRDNORM | POLLERR; + ASSERT_THAT(poll(&pollset, 1, -1), SyscallSucceeds()); + EXPECT_EQ(hdr->tp_status & TP_STATUS_USER, 1); + EXPECT_STREQ((char*)(hdr) + hdr->tp_net, kMessage.data()); + } + }); + sender.Join(); + receiver.Join(); +} + +TEST(PacketMmapTest, RawPacket) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + ASSERT_THAT(socket(AF_PACKET, SOCK_RAW, 0), SyscallFailsWithErrno(EPERM)); + GTEST_SKIP() << "Missing packet socket capability"; + } + sockaddr_ll bind_addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_ALL), + .sll_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()), + .sll_halen = ETH_ALEN, + }; + FileDescriptor mmap_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))); + + uint32_t tp_frame_size = 65536 + 128; + uint32_t tp_block_size = tp_frame_size * 32; + uint32_t tp_block_nr = 2; + uint32_t tp_frame_nr = (tp_block_size * tp_block_nr) / tp_frame_size; + tpacket_req req = { + .tp_block_size = tp_block_size, + .tp_block_nr = tp_block_nr, + .tp_frame_size = tp_frame_size, + .tp_frame_nr = tp_frame_nr, + }; + void* ring = ASSERT_NO_ERRNO_AND_VALUE(MakePacketMmapRing( + mmap_sock.get(), reinterpret_cast(&bind_addr), + sizeof(bind_addr), &req)); + auto ring_cleanup = Cleanup([ring, tp_block_size, tp_block_nr] { + ASSERT_THAT(munmap(ring, tp_block_size * tp_block_nr), SyscallSucceeds()); + }); + + char buffer[1024]; + struct ethhdr* eth = (struct ethhdr*)buffer; + char dest_mac[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + memcpy(eth->h_dest, dest_mac, ETH_ALEN); + char src_mac[] = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11}; + memcpy(eth->h_source, src_mac, ETH_ALEN); + eth->h_proto = htons(ETH_P_IP); + std::string kMessage = "123abc"; + memcpy(buffer + ETH_HLEN, kMessage.data(), kMessage.size()); + + ASSERT_THAT( + sendto(mmap_sock.get(), buffer, ETH_HLEN + kMessage.size(), 0 /* flags */, + reinterpret_cast(&bind_addr), sizeof(bind_addr)), + SyscallSucceeds()); + + tpacket_hdr* hdr = reinterpret_cast(ring); + struct pollfd pollset; + pollset.fd = mmap_sock.get(); + pollset.revents = 0; + pollset.events = POLLIN | POLLRDNORM | POLLERR; + ASSERT_THAT(poll(&pollset, 1, -1), SyscallSucceeds()); + EXPECT_EQ(hdr->tp_status & TP_STATUS_USER, 1); + EXPECT_EQ(hdr->tp_len, ETH_HLEN + kMessage.size()); + EXPECT_EQ(hdr->tp_snaplen, ETH_HLEN + kMessage.size()); + EXPECT_EQ(memcmp((char*)(hdr) + hdr->tp_mac, buffer, ETH_HLEN), 0); + EXPECT_STREQ((char*)(hdr) + hdr->tp_net, kMessage.c_str()); +} + +TEST(PacketMmapTest, SetRingAfterMmapFails) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + ASSERT_THAT(socket(AF_PACKET, SOCK_RAW, 0), SyscallFailsWithErrno(EPERM)); + GTEST_SKIP() << "Missing packet socket capability"; + } + sockaddr_ll bind_addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_ALL), + .sll_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()), + .sll_halen = ETH_ALEN, + }; + FileDescriptor mmap_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))); + + uint32_t tp_frame_size = 65536 + 128; + uint32_t tp_block_size = tp_frame_size * 32; + uint32_t tp_block_nr = 2; + uint32_t tp_frame_nr = (tp_block_size * tp_block_nr) / tp_frame_size; + tpacket_req req = { + .tp_block_size = tp_block_size, + .tp_block_nr = tp_block_nr, + .tp_frame_size = tp_frame_size, + .tp_frame_nr = tp_frame_nr, + }; + void* ring = ASSERT_NO_ERRNO_AND_VALUE(MakePacketMmapRing( + mmap_sock.get(), reinterpret_cast(&bind_addr), + sizeof(bind_addr), &req)); + auto ring_cleanup = Cleanup([ring, tp_block_size, tp_block_nr] { + ASSERT_THAT(munmap(ring, tp_block_size * tp_block_nr), SyscallSucceeds()); + }); + + EXPECT_THAT(setsockopt(mmap_sock.get(), SOL_PACKET, PACKET_RX_RING, &req, + sizeof(req)), + SyscallFailsWithErrno(EBUSY)); +} + +TEST(PacketMmapTest, MmapCopy) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + ASSERT_THAT(socket(AF_PACKET, SOCK_RAW, 0), SyscallFailsWithErrno(EPERM)); + GTEST_SKIP() << "Missing packet socket capability"; + } + sockaddr_ll bind_addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_ALL), + .sll_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()), + .sll_halen = ETH_ALEN, + }; + FileDescriptor mmap_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))); + + uint32_t tp_frame_size = 256; + uint32_t tp_block_size = tp_frame_size * 32; + uint32_t tp_block_nr = 2; + uint32_t tp_frame_nr = (tp_block_size * tp_block_nr) / tp_frame_size; + tpacket_req req = { + .tp_block_size = tp_block_size, + .tp_block_nr = tp_block_nr, + .tp_frame_size = tp_frame_size, + .tp_frame_nr = tp_frame_nr, + }; + void* ring = ASSERT_NO_ERRNO_AND_VALUE(MakePacketMmapRing( + mmap_sock.get(), reinterpret_cast(&bind_addr), + sizeof(bind_addr), &req)); + auto ring_cleanup = Cleanup([ring, tp_block_size, tp_block_nr] { + ASSERT_THAT(munmap(ring, tp_block_size * tp_block_nr), SyscallSucceeds()); + }); + + std::string kMessage = "123abc" + std::string(1000, '*'); + ASSERT_THAT( + sendto(mmap_sock.get(), kMessage.c_str(), kMessage.size(), 0 /* flags */, + reinterpret_cast(&bind_addr), sizeof(bind_addr)), + SyscallSucceeds()); + + // Wait for the packet to become available on both sockets. + struct pollfd pfd = {}; + pfd.fd = mmap_sock.get(); + pfd.revents = 0; + pfd.events = POLLIN | POLLRDNORM | POLLERR; + ASSERT_THAT(poll(&pfd, 1, -1), SyscallSucceeds()); + + char buf[1024]; + socklen_t src_len = sizeof(kMessage); + EXPECT_THAT(recvfrom(mmap_sock.get(), buf, sizeof(buf), 0, + reinterpret_cast(&bind_addr), &src_len), + SyscallSucceedsWithValue(kMessage.size())); + + tpacket_hdr* hdr = reinterpret_cast(ring); + EXPECT_EQ(hdr->tp_status & (TP_STATUS_USER | TP_STATUS_COPY), + TP_STATUS_USER | TP_STATUS_COPY); + EXPECT_EQ(hdr->tp_snaplen, tp_frame_size - hdr->tp_mac); +} + +} // namespace +} // namespace testing +} // namespace gvisor + +int main(int argc, char** argv) { + // Some tests depend on delivering a signal to the main thread. Block the + // target signal so that any other threads created by TestInit will also have + // the signal blocked. + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGALRM); + TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0); + + gvisor::testing::TestInit(&argc, &argv); + return gvisor::testing::RunAllTests(); +}