diff --git a/pkg/abi/linux/socket.go b/pkg/abi/linux/socket.go index cb864503a..ccf557b9c 100644 --- a/pkg/abi/linux/socket.go +++ b/pkg/abi/linux/socket.go @@ -146,8 +146,12 @@ const ( // Packet socket options from 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 . @@ -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 diff --git a/pkg/sentry/socket/netstack/netstack.go b/pkg/sentry/socket/netstack/netstack.go index 2ecceec59..4e9e4454c 100644 --- a/pkg/sentry/socket/netstack/netstack.go +++ b/pkg/sentry/socket/netstack/netstack.go @@ -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 } diff --git a/pkg/sentry/socket/netstack/packetmmap/endpoint.go b/pkg/sentry/socket/netstack/packetmmap/endpoint.go index 73161f15d..04f5804fe 100644 --- a/pkg/sentry/socket/netstack/packetmmap/endpoint.go +++ b/pkg/sentry/socket/netstack/packetmmap/endpoint.go @@ -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 diff --git a/pkg/tcpip/stack/registration.go b/pkg/tcpip/stack/registration.go index f92584c8e..77566dd0a 100644 --- a/pkg/tcpip/stack/registration.go +++ b/pkg/tcpip/stack/registration.go @@ -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 diff --git a/pkg/tcpip/tcpip.go b/pkg/tcpip/tcpip.go index bd2a65b67..6d516d3ab 100644 --- a/pkg/tcpip/tcpip.go +++ b/pkg/tcpip/tcpip.go @@ -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 ( diff --git a/pkg/tcpip/transport/packet/endpoint.go b/pkg/tcpip/transport/packet/endpoint.go index 72250e84e..f8cfdc2c7 100644 --- a/pkg/tcpip/transport/packet/endpoint.go +++ b/pkg/tcpip/transport/packet/endpoint.go @@ -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), } } diff --git a/test/syscalls/linux/packet_mmap.cc b/test/syscalls/linux/packet_mmap.cc index d9ba448c1..716e5878c 100644 --- a/test/syscalls/linux/packet_mmap.cc +++ b/test/syscalls/linux/packet_mmap.cc @@ -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(&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(&bind_addr), sizeof(bind_addr)), + SyscallSucceeds()); + + tpacket2_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()); + // 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