diff --git a/pkg/abi/linux/socket.go b/pkg/abi/linux/socket.go index 1b6b13afb..cb864503a 100644 --- a/pkg/abi/linux/socket.go +++ b/pkg/abi/linux/socket.go @@ -147,6 +147,7 @@ const ( // Packet socket options from const ( PACKET_RX_RING = 5 + PACKET_VERSION = 10 ) // Statuses for a frame in a packet_mmap ring buffer from . @@ -189,6 +190,23 @@ type TpacketHdr struct { _ [4]byte } +// Tpacket2Hdr is the header for a frame in a packet_mmap ring buffer from +// . +// +// +marshal +type Tpacket2Hdr struct { + TpStatus uint32 + TpLen uint32 + TpSnaplen uint32 + TpMac uint16 + TpNet uint16 + TpSec uint32 + TpNSec uint32 + TpVlanTci uint16 + TpVlanTpid uint16 + _ [4]uint8 +} + // TpacketAlignment is the alignment of a frame in a packet_mmap ring buffer // from . const ( @@ -198,12 +216,17 @@ const ( // TPACKET_V1 is the version of a packet_mmap ring buffer from // that is implemented in gVisor. const ( + // TPACKET_V1 is the default version of PACKET_MMAP. TPACKET_V1 = iota + // TPACKET_V2 is the version of PACKET_MMAP for tpacket2_hdr. + TPACKET_V2 ) -// TPACKET_HDRLEN is the length of a TpacketHdr from . var ( - TPACKET_HDRLEN = TPacketAlign(uint32((*TpacketHdr)(nil).SizeBytes())) + uint32((*SockAddrLink)(nil).SizeBytes()) + // TPACKET_HDRLEN is the length of a TpacketHdr from . + TPACKET_HDRLEN = TPacketAlign(uint32((*TpacketHdr)(nil).SizeBytes()) + uint32((*SockAddrLink)(nil).SizeBytes())) + // TPACKET2_HDRLEN is the length of a Tpacket2Hdr from . + TPACKET2_HDRLEN = TPacketAlign(uint32((*Tpacket2Hdr)(nil).SizeBytes()) + uint32((*SockAddrLink)(nil).SizeBytes())) ) // TPacketAlign aligns a value to the alignment of a TPacket. diff --git a/pkg/sentry/socket/netstack/netstack.go b/pkg/sentry/socket/netstack/netstack.go index 540b84cf6..f94e1a860 100644 --- a/pkg/sentry/socket/netstack/netstack.go +++ b/pkg/sentry/socket/netstack/netstack.go @@ -2749,8 +2749,13 @@ func setSockOptPacket(t *kernel.Task, s socket.Socket, ep commonEndpoint, name i } else { return syserr.ErrNotSupported } + return nil + case linux.PACKET_VERSION: + v := hostarch.ByteOrder.Uint32(optVal) + return syserr.TranslateNetstackError(ep.SetSockOptInt(tcpip.PacketMMapVersionOption, int(v))) + default: + return syserr.ErrNotSupported } - return nil } // GetSockName implements the linux syscall getsockname(2) for sockets backed by diff --git a/pkg/sentry/socket/netstack/packetmmap/endpoint.go b/pkg/sentry/socket/netstack/packetmmap/endpoint.go index 9e779ab68..ddaf4618f 100644 --- a/pkg/sentry/socket/netstack/packetmmap/endpoint.go +++ b/pkg/sentry/socket/netstack/packetmmap/endpoint.go @@ -66,6 +66,7 @@ type Endpoint struct { mode ringBufferMode nicID tcpip.NICID netProto tcpip.NetworkProtocolNumber + version int headerLen uint32 stack *stack.Stack @@ -88,7 +89,15 @@ func (m *Endpoint) Init(ctx context.Context, opts stack.PacketMMapOpts) error { m.stats = opts.Stats m.nicID = opts.NICID m.netProto = opts.NetProto - m.headerLen = linux.TPACKET_HDRLEN + m.version = opts.Version + switch m.version { + case linux.TPACKET_V1: + m.headerLen = linux.TPACKET_HDRLEN + case linux.TPACKET_V2: + m.headerLen = linux.TPACKET2_HDRLEN + default: + panic(fmt.Sprintf("invalid version %d supplied to InitPacketMMap", m.version)) + } if opts.Req.TpBlockNr != 0 { if opts.Req.TpBlockSize <= 0 { return linuxerr.EINVAL @@ -378,22 +387,43 @@ func (m *Endpoint) marshalSockAddr(pkt *stack.PacketBuffer, view *buffer.View) { hdr := header.Ethernet(pkt.LinkHeader().Slice()) copy(sll.HardwareAddr[:], hdr.SourceAddress()) } - hdrSize := uint32((*linux.TpacketHdr)(nil).SizeBytes()) + var hdrSize uint32 + if m.version == linux.TPACKET_V2 { + hdrSize = uint32((*linux.Tpacket2Hdr)(nil).SizeBytes()) + } else { + hdrSize = uint32((*linux.TpacketHdr)(nil).SizeBytes()) + } sll.MarshalBytes(view.AsSlice()[linux.TPacketAlign(hdrSize):]) } func (m *Endpoint) marshalFrameHeader(pktBuf buffer.Buffer, macOffset, netOffset, dataLength uint32, view *buffer.View) { t := m.stack.Clock().Now() - hdr := linux.TpacketHdr{ - // The status is set separately to ensure the frame is written before the - // status is set. - TpStatus: linux.TP_STATUS_KERNEL, - TpLen: uint32(pktBuf.Size()), - TpSnaplen: dataLength, - TpMac: uint16(macOffset), - TpNet: uint16(netOffset), - TpSec: uint32(t.Unix()), - TpUsec: uint32(t.UnixMicro() % 1e6), + switch m.version { + case linux.TPACKET_V1: + hdr := linux.TpacketHdr{ + // The status is set separately to ensure the frame is written before the + // status is set. + TpStatus: linux.TP_STATUS_KERNEL, + TpLen: uint32(pktBuf.Size()), + TpSnaplen: dataLength, + TpMac: uint16(macOffset), + TpNet: uint16(netOffset), + TpSec: uint32(t.Unix()), + TpUsec: uint32(t.UnixMicro() % 1e6), + } + hdr.MarshalBytes(view.AsSlice()) + case linux.TPACKET_V2: + hdr := linux.Tpacket2Hdr{ + TpStatus: linux.TP_STATUS_KERNEL, + TpLen: uint32(pktBuf.Size()), + TpSnaplen: dataLength, + TpMac: uint16(macOffset), + TpNet: uint16(netOffset), + TpSec: uint32(t.Unix()), + TpNSec: uint32(t.UnixNano() % 1e9), + } + hdr.MarshalBytes(view.AsSlice()) + default: + panic(fmt.Sprintf("invalid version %d supplied to HandlePacket", m.version)) } - hdr.MarshalBytes(view.AsSlice()) } diff --git a/pkg/tcpip/stack/registration.go b/pkg/tcpip/stack/registration.go index eef4ef869..f92584c8e 100644 --- a/pkg/tcpip/stack/registration.go +++ b/pkg/tcpip/stack/registration.go @@ -208,6 +208,7 @@ type PacketMMapOpts struct { NICID tcpip.NICID NetProto tcpip.NetworkProtocolNumber PacketEndpoint MappablePacketEndpoint + Version int } // PacketMMapEndpoint is the interface implemented by endpoints to handle memory diff --git a/pkg/tcpip/tcpip.go b/pkg/tcpip/tcpip.go index 970fe21d2..bd2a65b67 100644 --- a/pkg/tcpip/tcpip.go +++ b/pkg/tcpip/tcpip.go @@ -996,6 +996,9 @@ const ( // IPv6Checksum is used to request the stack to populate and validate the IPv6 // checksum for transport level headers. IPv6Checksum + + // PacketMMapVersionOption is used to set the packet mmap version. + PacketMMapVersionOption ) const ( diff --git a/pkg/tcpip/transport/packet/endpoint.go b/pkg/tcpip/transport/packet/endpoint.go index 10e222d97..5e51f379f 100644 --- a/pkg/tcpip/transport/packet/endpoint.go +++ b/pkg/tcpip/transport/packet/endpoint.go @@ -36,6 +36,13 @@ import ( "gvisor.dev/gvisor/pkg/waiter" ) +type tpacketVersion int + +const ( + tpacketVersion1 tpacketVersion = iota + tpacketVersion2 +) + var _ stack.MappablePacketEndpoint = (*endpoint)(nil) // +stateify savable @@ -96,6 +103,7 @@ type endpoint struct { packetMmapRxConfig *tcpip.TpacketReq packetMmapTxConfig *tcpip.TpacketReq + packetMMapVersion tpacketVersion packetMMapEp stack.PacketMMapEndpoint } @@ -391,8 +399,24 @@ func (ep *endpoint) SetSockOpt(opt tcpip.SettableSocketOption) tcpip.Error { } // SetSockOptInt implements tcpip.Endpoint.SetSockOptInt. -func (*endpoint) SetSockOptInt(tcpip.SockOptInt, int) tcpip.Error { - return &tcpip.ErrUnknownProtocolOption{} +func (ep *endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) tcpip.Error { + switch opt { + case tcpip.PacketMMapVersionOption: + // We support up to TPACKET_V2. + version := tpacketVersion(v) + switch version { + case tpacketVersion1, tpacketVersion2: + if ep.packetMMapEp != nil { + return &tcpip.ErrEndpointBusy{} + } + ep.packetMMapVersion = version + return nil + default: + return &tcpip.ErrInvalidOptionValue{} + } + default: + return &tcpip.ErrUnknownProtocolOption{} + } } func (ep *endpoint) LastError() tcpip.Error { @@ -544,6 +568,7 @@ func (ep *endpoint) GetPacketMMapOpts(req *tcpip.TpacketReq, isRx bool) stack.Pa NICID: ep.boundNIC, NetProto: ep.boundNetProto, PacketEndpoint: ep, + Version: int(ep.packetMMapVersion), } } diff --git a/test/syscalls/linux/packet_mmap.cc b/test/syscalls/linux/packet_mmap.cc index c6506326f..1e336aacf 100644 --- a/test/syscalls/linux/packet_mmap.cc +++ b/test/syscalls/linux/packet_mmap.cc @@ -46,7 +46,10 @@ namespace testing { namespace { PosixErrorOr MakePacketMmapRing(int fd, const sockaddr* bind_addr, - int bind_addr_size, tpacket_req* req) { + int bind_addr_size, tpacket_req* req, + int version = TPACKET_V1) { + RETURN_ERROR_IF_SYSCALL_FAIL( + setsockopt(fd, SOL_PACKET, PACKET_VERSION, &version, sizeof(version))); 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)); @@ -463,6 +466,81 @@ TEST(PacketMmapTest, MmapCopy) { EXPECT_EQ(hdr->tp_snaplen, tp_frame_size - hdr->tp_mac); } +TEST(PacketMmapTest, SetVersion) { + 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)); + + int version = TPACKET_V2; + EXPECT_THAT(setsockopt(mmap_sock.get(), SOL_PACKET, PACKET_VERSION, &version, + sizeof(version)), + SyscallSucceeds()); + version = TPACKET_V1; + EXPECT_THAT(setsockopt(mmap_sock.get(), SOL_PACKET, PACKET_VERSION, &version, + sizeof(version)), + SyscallSucceeds()); + version = TPACKET_V3; + EXPECT_THAT(setsockopt(mmap_sock.get(), SOL_PACKET, PACKET_VERSION, &version, + sizeof(version)), + SyscallFailsWithErrno(EINVAL)); + version = TPACKET_V1 + 100; + EXPECT_THAT(setsockopt(mmap_sock.get(), SOL_PACKET, PACKET_VERSION, &version, + sizeof(version)), + SyscallFailsWithErrno(EINVAL)); +} + +TEST(PacketMmapTest, BasicV2) { + 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, 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()); +} + } // namespace } // namespace testing } // namespace gvisor