Implement MappablePacketEndpoint for PACKET_MMAP and add tests.

PiperOrigin-RevId: 723590936
This commit is contained in:
Lucas Manning
2025-02-05 11:39:27 -08:00
committed by gVisor bot
parent e37e6814d3
commit 83a4caf2a7
7 changed files with 641 additions and 18 deletions
+3 -2
View File
@@ -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 <linux/if_packet.h>.
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.
+2
View File
@@ -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",
+55 -4
View File
@@ -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
}
+73 -12
View File
@@ -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
}
+5
View File
@@ -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",
)
+22
View File
@@ -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,
+481
View File
@@ -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 <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <poll.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <cstdint>
#include <cstring>
#include <string>
#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<void*> 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<const sockaddr*>(&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<const sockaddr*>(&bind_addr), sizeof(bind_addr)),
SyscallSucceeds());
tpacket_hdr* hdr = reinterpret_cast<tpacket_hdr*>(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<const sockaddr*>(&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<const sockaddr*>(&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<const sockaddr*>(&bind_addr),
sizeof(bind_addr)),
SyscallSucceeds());
struct tpacket_hdr* hdr = reinterpret_cast<struct tpacket_hdr*>(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<struct tpacket_hdr*>(
reinterpret_cast<char*>(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<tpacket_hdr*>(
(reinterpret_cast<char*>(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<const sockaddr*>(&bind_addr),
sizeof(bind_addr)),
SyscallSucceeds());
hdr = reinterpret_cast<struct tpacket_hdr*>(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<const sockaddr*>(&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<const sockaddr*>(&bind_addr),
sizeof(bind_addr)),
SyscallSucceeds());
}
});
ScopedThread receiver([&] {
struct tpacket_hdr* hdr = reinterpret_cast<struct tpacket_hdr*>(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<const sockaddr*>(&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<const sockaddr*>(&bind_addr), sizeof(bind_addr)),
SyscallSucceeds());
tpacket_hdr* hdr = reinterpret_cast<tpacket_hdr*>(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<const sockaddr*>(&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<const sockaddr*>(&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<const sockaddr*>(&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<sockaddr*>(&bind_addr), &src_len),
SyscallSucceedsWithValue(kMessage.size()));
tpacket_hdr* hdr = reinterpret_cast<tpacket_hdr*>(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();
}