mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement GetSockOpt PACKET_HDRLEN and add a test for tcpdump.
PiperOrigin-RevId: 724445536
This commit is contained in:
committed by
gVisor bot
parent
de6637c27c
commit
9c490f813d
@@ -146,8 +146,12 @@ const (
|
||||
|
||||
// Packet socket options from <linux/if_packet.h>
|
||||
const (
|
||||
PACKET_RX_RING = 5
|
||||
PACKET_VERSION = 10
|
||||
PACKET_ADD_MEMBERSHIP = 1
|
||||
PACKET_RX_RING = 5
|
||||
PACKET_AUXDATA = 8
|
||||
PACKET_VERSION = 10
|
||||
PACKET_HDRLEN = 11
|
||||
PACKET_RESERVE = 12
|
||||
)
|
||||
|
||||
// Statuses for a frame in a packet_mmap ring buffer from <linux/if_packet.h>.
|
||||
@@ -187,7 +191,7 @@ type TpacketHdr struct {
|
||||
TpNet uint16
|
||||
TpSec uint32
|
||||
TpUsec uint32
|
||||
_ [4]byte
|
||||
_ [4]uint8
|
||||
}
|
||||
|
||||
// Tpacket2Hdr is the header for a frame in a packet_mmap ring buffer from
|
||||
|
||||
@@ -907,9 +907,9 @@ func GetSockOpt(t *kernel.Task, s socket.Socket, ep commonEndpoint, family int,
|
||||
case linux.SOL_ICMPV6:
|
||||
return getSockOptICMPv6(t, s, ep, name, outLen)
|
||||
|
||||
case linux.SOL_UDP,
|
||||
linux.SOL_RAW,
|
||||
linux.SOL_PACKET:
|
||||
case linux.SOL_PACKET:
|
||||
return getSockOptPacket(t, s, ep, name, outPtr, outLen)
|
||||
case linux.SOL_UDP, linux.SOL_RAW:
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
@@ -1850,6 +1850,29 @@ func getSockOptIP(t *kernel.Task, s socket.Socket, ep commonEndpoint, name int,
|
||||
return nil, syserr.ErrProtocolNotAvailable
|
||||
}
|
||||
|
||||
func getSockOptPacket(t *kernel.Task, s socket.Socket, ep commonEndpoint, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) {
|
||||
if _, ok := ep.(tcpip.Endpoint); !ok {
|
||||
log.Warningf("SOL_PACKET options not supported on endpoints other than tcpip.Endpoint: option = %d, endpoint = %T", name, ep)
|
||||
return nil, syserr.ErrUnknownProtocolOption
|
||||
}
|
||||
switch name {
|
||||
case linux.PACKET_HDRLEN:
|
||||
var version primitive.Int32
|
||||
version.CopyIn(t, outPtr)
|
||||
switch version {
|
||||
case linux.TPACKET_V1:
|
||||
v := primitive.Int32(uint32((*linux.TpacketHdr)(nil).SizeBytes()))
|
||||
return &v, nil
|
||||
case linux.TPACKET_V2:
|
||||
v := primitive.Int32(uint32((*linux.Tpacket2Hdr)(nil).SizeBytes()))
|
||||
return &v, nil
|
||||
default:
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
}
|
||||
return nil, syserr.ErrProtocolNotAvailable
|
||||
}
|
||||
|
||||
// SetSockOpt can be used to implement the linux syscall setsockopt(2) for
|
||||
// sockets backed by a commonEndpoint.
|
||||
func SetSockOpt(t *kernel.Task, s socket.Socket, ep commonEndpoint, level int, name int, optVal []byte) *syserr.Error {
|
||||
@@ -2761,6 +2784,12 @@ func setSockOptPacket(t *kernel.Task, s socket.Socket, ep commonEndpoint, name i
|
||||
}
|
||||
v := hostarch.ByteOrder.Uint32(optVal)
|
||||
return syserr.TranslateNetstackError(ep.SetSockOptInt(tcpip.PacketMMapVersionOption, int(v)))
|
||||
case linux.PACKET_RESERVE:
|
||||
v := hostarch.ByteOrder.Uint32(optVal)
|
||||
return syserr.TranslateNetstackError(ep.SetSockOptInt(tcpip.PacketMMapReserveOption, int(v)))
|
||||
case linux.PACKET_ADD_MEMBERSHIP, linux.PACKET_AUXDATA:
|
||||
// Silently ignore these options.
|
||||
return nil
|
||||
default:
|
||||
return syserr.ErrNotSupported
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ type Endpoint struct {
|
||||
|
||||
cooked bool
|
||||
packetEP stack.MappablePacketEndpoint
|
||||
reserve uint32
|
||||
nicID tcpip.NICID
|
||||
netProto tcpip.NetworkProtocolNumber
|
||||
version int
|
||||
@@ -99,6 +100,9 @@ func (m *Endpoint) Init(ctx context.Context, opts stack.PacketMMapOpts) error {
|
||||
m.nicID = opts.NICID
|
||||
m.netProto = opts.NetProto
|
||||
m.version = opts.Version
|
||||
m.reserve = opts.Reserve
|
||||
m.nicID = opts.NICID
|
||||
m.netProto = opts.NetProto
|
||||
switch m.version {
|
||||
case linux.TPACKET_V1:
|
||||
m.headerLen = linux.TPACKET_HDRLEN
|
||||
@@ -114,7 +118,7 @@ func (m *Endpoint) Init(ctx context.Context, opts stack.PacketMMapOpts) error {
|
||||
if opts.Req.TpBlockSize%hostarch.PageSize != 0 {
|
||||
return linuxerr.EINVAL
|
||||
}
|
||||
if opts.Req.TpFrameSize < m.headerLen {
|
||||
if opts.Req.TpFrameSize < m.headerLen+m.reserve {
|
||||
return linuxerr.EINVAL
|
||||
}
|
||||
if opts.Req.TpFrameSize&(linux.TPACKET_ALIGNMENT-1) != 0 {
|
||||
@@ -206,14 +210,14 @@ func (m *Endpoint) HandlePacket(nicID tcpip.NICID, netProto tcpip.NetworkProtoco
|
||||
pktBuf.TrimFront(int64(len(pkt.LinkHeader().Slice()) + len(pkt.VirtioNetHeader().Slice())))
|
||||
// Cooked packet endpoints don't include the link-headers in received
|
||||
// packets.
|
||||
netOffset = linux.TPacketAlign(m.headerLen + minMacLen)
|
||||
netOffset = linux.TPacketAlign(m.headerLen+minMacLen) + m.reserve
|
||||
macOffset = netOffset
|
||||
} else {
|
||||
virtioNetHdrLen := uint32(len(pkt.VirtioNetHeader().Slice()))
|
||||
macLen := uint32(len(pkt.LinkHeader().Slice())) + virtioNetHdrLen
|
||||
netOffset = linux.TPacketAlign(m.headerLen + macLen)
|
||||
netOffset = linux.TPacketAlign(m.headerLen+macLen) + m.reserve
|
||||
if macLen < minMacLen {
|
||||
netOffset = linux.TPacketAlign(m.headerLen + minMacLen)
|
||||
netOffset = linux.TPacketAlign(m.headerLen+minMacLen) + m.reserve
|
||||
}
|
||||
if virtioNetHdrLen > 0 {
|
||||
netOffset += virtioNetHdrLen
|
||||
|
||||
@@ -209,6 +209,7 @@ type PacketMMapOpts struct {
|
||||
NetProto tcpip.NetworkProtocolNumber
|
||||
PacketEndpoint MappablePacketEndpoint
|
||||
Version int
|
||||
Reserve uint32
|
||||
}
|
||||
|
||||
// PacketMMapEndpoint is the interface implemented by endpoints to handle memory
|
||||
|
||||
@@ -999,6 +999,10 @@ const (
|
||||
|
||||
// PacketMMapVersionOption is used to set the packet mmap version.
|
||||
PacketMMapVersionOption
|
||||
|
||||
// PacketMMapReserveOption is used to set the packet mmap reserved space
|
||||
// between the aligned header and the payload.
|
||||
PacketMMapReserveOption
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -104,6 +104,8 @@ type endpoint struct {
|
||||
// +checklocks:mu
|
||||
packetMMapVersion tpacketVersion
|
||||
// +checklocks:mu
|
||||
packetMMapReserve int
|
||||
// +checklocks:mu
|
||||
packetMMapEp stack.PacketMMapEndpoint
|
||||
}
|
||||
|
||||
@@ -418,6 +420,12 @@ func (ep *endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) tcpip.Error {
|
||||
default:
|
||||
return &tcpip.ErrInvalidOptionValue{}
|
||||
}
|
||||
case tcpip.PacketMMapReserveOption:
|
||||
if ep.packetMMapEp != nil {
|
||||
return &tcpip.ErrEndpointBusy{}
|
||||
}
|
||||
ep.packetMMapReserve = v
|
||||
return nil
|
||||
default:
|
||||
return &tcpip.ErrUnknownProtocolOption{}
|
||||
}
|
||||
@@ -575,6 +583,7 @@ func (ep *endpoint) GetPacketMMapOpts(req *tcpip.TpacketReq, isRx bool) stack.Pa
|
||||
NetProto: ep.boundNetProto,
|
||||
PacketEndpoint: ep,
|
||||
Version: int(ep.packetMMapVersion),
|
||||
Reserve: uint32(ep.packetMMapReserve),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -567,6 +567,93 @@ TEST(PacketMmapTest, BasicV2) {
|
||||
EXPECT_STREQ((char*)(hdr) + hdr->tp_net, kMessage.c_str());
|
||||
}
|
||||
|
||||
TEST(PacketMMmapTest, GetPacketHdrLen) {
|
||||
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));
|
||||
|
||||
int32_t val = TPACKET_V1;
|
||||
socklen_t val_len = sizeof(val);
|
||||
EXPECT_THAT(
|
||||
getsockopt(mmap_sock.get(), SOL_PACKET, PACKET_HDRLEN, &val, &val_len),
|
||||
SyscallSucceeds());
|
||||
EXPECT_EQ(val, sizeof(tpacket_hdr));
|
||||
|
||||
val = TPACKET_V2;
|
||||
EXPECT_THAT(
|
||||
getsockopt(mmap_sock.get(), SOL_PACKET, PACKET_HDRLEN, &val, &val_len),
|
||||
SyscallSucceeds());
|
||||
EXPECT_EQ(val, sizeof(tpacket2_hdr));
|
||||
|
||||
val = TPACKET_V3;
|
||||
EXPECT_THAT(
|
||||
getsockopt(mmap_sock.get(), SOL_PACKET, PACKET_HDRLEN, &val, &val_len),
|
||||
SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
TEST(PacketMmapTest, PacketReserve) {
|
||||
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,
|
||||
};
|
||||
|
||||
int reserve = 20;
|
||||
ASSERT_THAT(setsockopt(mmap_sock.get(), SOL_PACKET, PACKET_RESERVE, &reserve,
|
||||
sizeof(reserve)),
|
||||
SyscallSucceeds());
|
||||
void* ring = ASSERT_NO_ERRNO_AND_VALUE(MakePacketMmapRing(
|
||||
mmap_sock.get(), reinterpret_cast<const sockaddr*>(&bind_addr),
|
||||
sizeof(bind_addr), &req, TPACKET_V2));
|
||||
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());
|
||||
|
||||
tpacket2_hdr* hdr = reinterpret_cast<tpacket2_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());
|
||||
// PACKET_MMAP always adds a min 16 bytes between the sockaddr_ll and the
|
||||
// packet data.
|
||||
EXPECT_EQ(
|
||||
hdr->tp_net,
|
||||
TPACKET_ALIGN(sizeof(tpacket2_hdr) + sizeof(sockaddr_ll) + 16) + reserve);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
Reference in New Issue
Block a user