mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement MappablePacketEndpoint for PACKET_MMAP and add tests.
PiperOrigin-RevId: 723590936
This commit is contained in:
committed by
gVisor bot
parent
e37e6814d3
commit
83a4caf2a7
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user