Support SOL_IPV6 -> IPV6_CHECKSUM

PiperOrigin-RevId: 419164074
This commit is contained in:
Ghanan Gowripalan
2021-12-31 21:33:15 -08:00
committed by gVisor bot
parent 58b9bdfc21
commit b488df0a2f
7 changed files with 390 additions and 26 deletions
+22
View File
@@ -1343,6 +1343,19 @@ func getSockOptIPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name
}
switch name {
case linux.IPV6_CHECKSUM:
if outLen < sizeOfInt32 {
return nil, syserr.ErrInvalidArgument
}
v, err := ep.GetSockOptInt(tcpip.IPv6Checksum)
if err != nil {
return nil, syserr.TranslateNetstackError(err)
}
vP := primitive.Int32(v)
return &vP, nil
case linux.IPV6_V6ONLY:
if outLen < sizeOfInt32 {
return nil, syserr.ErrInvalidArgument
@@ -2151,6 +2164,15 @@ func setSockOptIPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name
}
switch name {
case linux.IPV6_CHECKSUM:
if len(optVal) < sizeOfInt32 {
return syserr.ErrInvalidArgument
}
// int may not be 32-bits so we cast the uint32 to an int32 before casting
// to an int.
return syserr.TranslateNetstackError(ep.SetSockOptInt(tcpip.IPv6Checksum, int(int32(hostarch.ByteOrder.Uint32(optVal)))))
case linux.IPV6_V6ONLY:
if len(optVal) < sizeOfInt32 {
return syserr.ErrInvalidArgument
+5
View File
@@ -24,6 +24,11 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/buffer"
)
// ChecksumSize is the size of a checksum.
//
// The checksum is held in a uint16 which is 2 bytes.
const ChecksumSize = 2
// PutChecksum puts the checksum in the provided byte slice.
func PutChecksum(b []byte, xsum uint16) {
binary.BigEndian.PutUint16(b, xsum)
+4 -4
View File
@@ -67,9 +67,9 @@ const (
// packet-too-big packet.
ICMPv6PacketTooBigMinimumSize = ICMPv6MinimumSize
// icmpv6ChecksumOffset is the offset of the checksum field
// ICMPv6ChecksumOffset is the offset of the checksum field
// in an ICMPv6 message.
icmpv6ChecksumOffset = 2
ICMPv6ChecksumOffset = 2
// icmpv6PointerOffset is the offset of the pointer
// in an ICMPv6 Parameter problem message.
@@ -194,12 +194,12 @@ func (b ICMPv6) SetTypeSpecific(val uint32) {
// Checksum is the ICMP checksum field.
func (b ICMPv6) Checksum() uint16 {
return binary.BigEndian.Uint16(b[icmpv6ChecksumOffset:])
return binary.BigEndian.Uint16(b[ICMPv6ChecksumOffset:])
}
// SetChecksum sets the ICMP checksum field.
func (b ICMPv6) SetChecksum(checksum uint16) {
PutChecksum(b[icmpv6ChecksumOffset:], checksum)
PutChecksum(b[ICMPv6ChecksumOffset:], checksum)
}
// SourcePort implements Transport.SourcePort.
+4
View File
@@ -757,6 +757,10 @@ const (
//
// NOTE: This option is currently only stubed out and is a no-op
TCPWindowClampOption
// IPv6Checksum is used to request the stack to populate and validate the IPv6
// checksum for transport level headers.
IPv6Checksum
)
const (
+91 -6
View File
@@ -85,6 +85,13 @@ type endpoint struct {
rcvDisabled bool
mu sync.RWMutex `state:"nosave"`
// ipv6ChecksumOffset indicates the offset to populate the IPv6 checksum at.
//
// A negative value indicates no checksum should be calculated.
//
// +checklocks:mu
ipv6ChecksumOffset int
// icmp6Filter holds the filter for ICMPv6 packets.
//
// +checklocks:mu
@@ -97,11 +104,24 @@ func NewEndpoint(stack *stack.Stack, netProto tcpip.NetworkProtocolNumber, trans
}
func newEndpoint(s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, waiterQueue *waiter.Queue, associated bool) (tcpip.Endpoint, tcpip.Error) {
// Calculating the upper-layer checksum is disabled by default for raw IPv6
// endpoints, unless the upper-layer protocol is ICMPv6.
//
// As per RFC 3542 section 3.1,
//
// The kernel will calculate and insert the ICMPv6 checksum for ICMPv6
// raw sockets, since this checksum is mandatory.
ipv6ChecksumOffset := -1
if netProto == header.IPv6ProtocolNumber && transProto == header.ICMPv6ProtocolNumber {
ipv6ChecksumOffset = header.ICMPv6ChecksumOffset
}
e := &endpoint{
stack: s,
transProto: transProto,
waiterQueue: waiterQueue,
associated: associated,
stack: s,
transProto: transProto,
waiterQueue: waiterQueue,
associated: associated,
ipv6ChecksumOffset: ipv6ChecksumOffset,
}
e.ops.InitHandler(e, e.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits)
e.ops.SetHeaderIncluded(!associated)
@@ -274,7 +294,10 @@ func (e *endpoint) Write(p tcpip.Payloader, opts tcpip.WriteOptions) (int64, tcp
}
func (e *endpoint) write(p tcpip.Payloader, opts tcpip.WriteOptions) (int64, tcpip.Error) {
e.mu.Lock()
ctx, err := e.net.AcquireContextForWrite(opts)
ipv6ChecksumOffset := e.ipv6ChecksumOffset
e.mu.Unlock()
if err != nil {
return 0, err
}
@@ -285,6 +308,18 @@ func (e *endpoint) write(p tcpip.Payloader, opts tcpip.WriteOptions) (int64, tcp
return 0, &tcpip.ErrBadBuffer{}
}
if packetInfo := ctx.PacketInfo(); packetInfo.NetProto == header.IPv6ProtocolNumber && ipv6ChecksumOffset >= 0 {
// Make sure we can fit the checksum.
if len(payloadBytes) < ipv6ChecksumOffset+header.ChecksumSize {
return 0, &tcpip.ErrInvalidOptionValue{}
}
xsum := header.PseudoHeaderChecksum(e.transProto, packetInfo.LocalAddress, packetInfo.RemoteAddress, uint16(len(payloadBytes)))
header.PutChecksum(payloadBytes[ipv6ChecksumOffset:], 0)
xsum = header.Checksum(payloadBytes, xsum)
header.PutChecksum(payloadBytes[ipv6ChecksumOffset:], ^xsum)
}
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(ctx.PacketInfo().MaxHeaderLength),
Data: buffer.View(payloadBytes).ToVectorisedView(),
@@ -415,7 +450,31 @@ func (e *endpoint) SetSockOpt(opt tcpip.SettableSocketOption) tcpip.Error {
}
func (e *endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) tcpip.Error {
return e.net.SetSockOptInt(opt, v)
switch opt {
case tcpip.IPv6Checksum:
if e.net.NetProto() != header.IPv6ProtocolNumber {
return &tcpip.ErrUnknownProtocolOption{}
}
if e.transProto == header.ICMPv6ProtocolNumber {
// As per RFC 3542 section 3.1,
//
// An attempt to set IPV6_CHECKSUM for an ICMPv6 socket will fail.
return &tcpip.ErrInvalidOptionValue{}
}
// Make sure the offset is aligned properly if checksum is requested.
if v > 0 && v%header.ChecksumSize != 0 {
return &tcpip.ErrInvalidOptionValue{}
}
e.mu.Lock()
defer e.mu.Unlock()
e.ipv6ChecksumOffset = v
return nil
default:
return e.net.SetSockOptInt(opt, v)
}
}
// GetSockOpt implements tcpip.Endpoint.GetSockOpt.
@@ -453,6 +512,15 @@ func (e *endpoint) GetSockOptInt(opt tcpip.SockOptInt) (int, tcpip.Error) {
e.rcvMu.Unlock()
return v, nil
case tcpip.IPv6Checksum:
if e.net.NetProto() != header.IPv6ProtocolNumber {
return 0, &tcpip.ErrUnknownProtocolOption{}
}
e.mu.Lock()
defer e.mu.Unlock()
return e.ipv6ChecksumOffset, nil
default:
return e.net.GetSockOptInt(opt)
}
@@ -552,6 +620,7 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
headers = append(headers, networkHeader...)
headers = append(headers, transportHeader...)
combinedVV = headers.ToVectorisedView()
combinedVV.Append(pkt.Data().ExtractVV())
case header.IPv6ProtocolNumber:
if e.transProto == header.ICMPv6ProtocolNumber {
if len(transportHeader) < header.ICMPv6MinimumSize {
@@ -564,10 +633,26 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
}
combinedVV = append(buffer.View(nil), transportHeader...).ToVectorisedView()
combinedVV.Append(pkt.Data().ExtractVV())
if checksumOffset := e.ipv6ChecksumOffset; checksumOffset >= 0 {
vvSize := combinedVV.Size()
if vvSize < checksumOffset+header.ChecksumSize {
// Message too small to fit checksum.
return false
}
xsum := header.PseudoHeaderChecksum(e.transProto, srcAddr, dstAddr, uint16(vvSize))
xsum = header.ChecksumVV(combinedVV, xsum)
if xsum != 0xFFFF {
// Invalid checksum.
return false
}
}
default:
panic(fmt.Sprintf("unrecognized protocol number = %d", info.NetProto))
}
combinedVV.Append(pkt.Data().ExtractVV())
packet.data = combinedVV
packet.receivedAt = e.stack.Clock().Now()
+173
View File
@@ -1186,6 +1186,179 @@ TEST(RawSocketTest, ReceiveIPv6PacketInfo) {
EXPECT_THAT(CMSG_NXTHDR(&recv_msg, cmsg), IsNull());
}
TEST(RawSocketTest, SetIPv6ChecksumError_MultipleOf2) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability()));
FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_RAW, IPPROTO_UDP));
int intV = 3;
ASSERT_THAT(
setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &intV, sizeof(intV)),
SyscallFailsWithErrno(EINVAL));
intV = 5;
ASSERT_THAT(
setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &intV, sizeof(intV)),
SyscallFailsWithErrno(EINVAL));
intV = 2;
ASSERT_THAT(
setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &intV, sizeof(intV)),
SyscallSucceeds());
intV = 4;
ASSERT_THAT(
setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &intV, sizeof(intV)),
SyscallSucceeds());
}
TEST(RawSocketTest, SetIPv6ChecksumError_ReadShort) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability()));
FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_RAW, IPPROTO_UDP));
int intV = 2;
if (IsRunningOnGvisor() && !IsRunningWithHostinet()) {
// TODO(https://gvisor.dev/issue/6982): This is a deviation from Linux. We
// should determine if we want to match the behaviour or handle the error
// more gracefully.
ASSERT_THAT(
setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &intV, sizeof(intV) - 1),
SyscallFailsWithErrno(EINVAL));
return;
}
intV = std::numeric_limits<int>::max();
if (intV % 2) {
intV--;
}
if (const char* val = getenv("IPV6_CHECKSUM_SETSOCKOPT_SHORT_EXCEPTION");
val != nullptr && strcmp(val, "1") == 0) {
// TODO(https://issuetracker.google.com/issues/212585236): As of writing, it
// seems like at least one Linux environment considers optlen unlike a local
// Linux environment. In this case we call setsockopt with the full int so
// that the rest of the test passes. Once the root cause for this difference
// is found, we can update this check.
ASSERT_THAT(
setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &intV, sizeof(intV)),
SyscallSucceeds());
} else {
ASSERT_THAT(
setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &intV, sizeof(intV) - 1),
SyscallSucceeds());
}
{
int got;
socklen_t got_len = sizeof(got);
ASSERT_THAT(getsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &got, &got_len),
SyscallSucceeds());
ASSERT_EQ(got_len, sizeof(got));
// Even though we called setsockopt with a length smaller than an int, Linux
// seems to read the full int.
EXPECT_EQ(got, intV);
}
// If we have pass a pointer that points to memory less than the size of an
// int, we get a bad address error.
std::unique_ptr<uint8_t> u8V;
// Linux seems to assume a full int but doesn't check the passed length.
//
// https://github.com/torvalds/linux/blob/a52a8e9eaf4a12dd58953fc622bb2bc08fd1d32c/net/ipv6/raw.c#L1023
// shows that Linux copies optVal to an int without first checking optLen.
ASSERT_THAT(
setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, u8V.get(), sizeof(*u8V)),
SyscallFailsWithErrno(EFAULT));
}
TEST(RawSocketTest, IPv6Checksum_ValidateAndCalculate) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability()));
FileDescriptor checksum_set =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_RAW, IPPROTO_UDP));
FileDescriptor checksum_not_set =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_RAW, IPPROTO_UDP));
const sockaddr_in6 addr = {
.sin6_family = AF_INET6,
.sin6_addr = IN6ADDR_LOOPBACK_INIT,
};
auto bind_and_set_checksum = [&](const FileDescriptor& fd, int v) {
ASSERT_THAT(
bind(fd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)),
SyscallSucceeds());
int got;
socklen_t got_len = sizeof(got);
ASSERT_THAT(getsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &got, &got_len),
SyscallSucceeds());
ASSERT_EQ(got_len, sizeof(got));
EXPECT_EQ(got, -1);
ASSERT_THAT(setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &v, sizeof(v)),
SyscallSucceeds());
ASSERT_THAT(getsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &got, &got_len),
SyscallSucceeds());
ASSERT_EQ(got_len, sizeof(got));
EXPECT_EQ(got, v);
};
struct udp_packet {
udphdr udp;
uint32_t value;
} ABSL_ATTRIBUTE_PACKED;
ASSERT_NO_FATAL_FAILURE(bind_and_set_checksum(
checksum_set, offsetof(udp_packet, udp) + offsetof(udphdr, uh_sum)));
ASSERT_NO_FATAL_FAILURE(bind_and_set_checksum(checksum_not_set, -1));
auto send = [&](const FileDescriptor& fd, uint32_t v) {
const udp_packet packet = {
.value = v,
};
ASSERT_THAT(sendto(fd.get(), &packet, sizeof(packet), /*flags=*/0,
reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)),
SyscallSucceedsWithValue(sizeof(packet)));
};
auto expect_receive = [&](const FileDescriptor& fd, uint32_t v,
bool should_check_xsum) {
udp_packet packet;
sockaddr_in6 sender;
socklen_t sender_len = sizeof(sender);
ASSERT_THAT(
RetryEINTR(recvfrom)(fd.get(), &packet, sizeof(packet), /*flags=*/0,
reinterpret_cast<sockaddr*>(&sender), &sender_len),
SyscallSucceedsWithValue(sizeof(packet)));
ASSERT_EQ(sender_len, sizeof(sender));
EXPECT_EQ(memcmp(&sender, &addr, sizeof(addr)), 0);
EXPECT_EQ(packet.value, v);
if (should_check_xsum) {
EXPECT_NE(packet.udp.uh_sum, 0);
} else {
EXPECT_EQ(packet.udp.uh_sum, 0);
}
};
uint32_t counter = 1;
// Packets sent through checksum_not_set will not have a valid checksum set so
// checksum_set should not accept those packets.
ASSERT_NO_FATAL_FAILURE(send(checksum_not_set, counter));
ASSERT_NO_FATAL_FAILURE(expect_receive(checksum_not_set, counter, false));
// Packets sent through checksum_set will have a valid checksum so both
// sockets should accept them.
ASSERT_NO_FATAL_FAILURE(send(checksum_set, ++counter));
ASSERT_NO_FATAL_FAILURE(expect_receive(checksum_set, counter, true));
ASSERT_NO_FATAL_FAILURE(expect_receive(checksum_not_set, counter, true));
}
} // namespace
} // namespace testing
+91 -16
View File
@@ -109,7 +109,7 @@ void RawSocketICMPTest::TearDown() {
}
}
TEST_F(RawSocketICMPTest, SockOptIPv6Checksum) {
TEST_F(RawSocketICMPTest, IPv6ChecksumNotSupported) {
int v;
EXPECT_THAT(setsockopt(s_, SOL_IPV6, IPV6_CHECKSUM, &v, sizeof(v)),
SyscallFailsWithErrno(ENOPROTOOPT));
@@ -633,6 +633,92 @@ TEST_F(RawSocketICMPv6Test, GetPartialFilterSucceeds) {
FieldsAre(ElementsAreArray(expected_filter.icmp6_filt)));
}
TEST_F(RawSocketICMPv6Test, SetSockOptIPv6ChecksumFails) {
int v = 2;
EXPECT_THAT(setsockopt(fd().get(), SOL_IPV6, IPV6_CHECKSUM, &v, sizeof(v)),
SyscallFailsWithErrno(EINVAL));
socklen_t len = sizeof(v);
EXPECT_THAT(getsockopt(fd().get(), SOL_IPV6, IPV6_CHECKSUM, &v, &len),
SyscallSucceeds());
ASSERT_EQ(len, sizeof(v));
EXPECT_EQ(v, offsetof(icmp6_hdr, icmp6_cksum));
}
TEST_F(RawSocketICMPv6Test, MsgTooSmallToFillChecksumFailsSend) {
char buf[offsetof(icmp6_hdr, icmp6_cksum) +
sizeof((icmp6_hdr{}).icmp6_cksum) - 1];
const sockaddr_in6 addr = {
.sin6_family = AF_INET6,
.sin6_addr = IN6ADDR_LOOPBACK_INIT,
};
ASSERT_THAT(sendto(fd().get(), &buf, sizeof(buf), /*flags=*/0,
reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)),
SyscallFailsWithErrno(EINVAL));
}
constexpr uint8_t kUnusedICMPCode = 0;
TEST_F(RawSocketICMPv6Test, PingSuccessfully) {
// Only observe echo packets.
{
icmp6_filter set_filter;
ICMP6_FILTER_SETBLOCKALL(&set_filter);
ICMP6_FILTER_SETPASS(ICMP6_ECHO_REQUEST, &set_filter);
ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &set_filter);
ASSERT_THAT(setsockopt(fd().get(), SOL_ICMPV6, ICMP6_FILTER, &set_filter,
sizeof(set_filter)),
SyscallSucceeds());
}
const sockaddr_in6 addr = {
.sin6_family = AF_INET6,
.sin6_addr = IN6ADDR_LOOPBACK_INIT,
};
auto send_with_checksum = [&](uint16_t checksum) {
const icmp6_hdr echo_request = {
.icmp6_type = ICMP6_ECHO_REQUEST,
.icmp6_code = kUnusedICMPCode,
.icmp6_cksum = checksum,
};
ASSERT_THAT(RetryEINTR(sendto)(fd().get(), &echo_request,
sizeof(echo_request), /*flags=*/0,
reinterpret_cast<const sockaddr*>(&addr),
sizeof(addr)),
SyscallSucceedsWithValue(sizeof(echo_request)));
};
auto check_recv = [&](uint8_t expected_type) {
icmp6_hdr got_echo;
sockaddr_in6 sender;
socklen_t sender_len = sizeof(sender);
ASSERT_THAT(RetryEINTR(recvfrom)(
fd().get(), &got_echo, sizeof(got_echo), /*flags=*/0,
reinterpret_cast<sockaddr*>(&sender), &sender_len),
SyscallSucceedsWithValue(sizeof(got_echo)));
ASSERT_EQ(sender_len, sizeof(sender));
EXPECT_EQ(memcmp(&sender, &addr, sizeof(addr)), 0);
EXPECT_THAT(got_echo,
FieldsAre(expected_type, kUnusedICMPCode,
// The stack should have populated the checksum.
/*icmp6_cksum=*/Not(0), /*icmp6_dataun=*/_));
EXPECT_THAT(got_echo.icmp6_data32, ElementsAre(0));
};
// Send a request and observe the request followed by the response.
ASSERT_NO_FATAL_FAILURE(send_with_checksum(0));
ASSERT_NO_FATAL_FAILURE(check_recv(ICMP6_ECHO_REQUEST));
ASSERT_NO_FATAL_FAILURE(check_recv(ICMP6_ECHO_REPLY));
// The stack ignores the checksum set by the user.
ASSERT_NO_FATAL_FAILURE(send_with_checksum(1));
ASSERT_NO_FATAL_FAILURE(check_recv(ICMP6_ECHO_REQUEST));
ASSERT_NO_FATAL_FAILURE(check_recv(ICMP6_ECHO_REPLY));
}
class RawSocketICMPv6TypeTest : public RawSocketICMPv6Test,
public WithParamInterface<uint8_t> {};
@@ -664,7 +750,6 @@ TEST_P(RawSocketICMPv6TypeTest, FilterDeliveredPackets) {
// Send an ICMP packet for each type.
uint8_t icmp_type = 0;
constexpr uint8_t kUnusedICMPCode = 0;
do {
const icmp6_hdr packet = {
.icmp6_type = icmp_type,
@@ -685,25 +770,15 @@ TEST_P(RawSocketICMPv6TypeTest, FilterDeliveredPackets) {
sockaddr_in6 sender;
socklen_t sender_len = sizeof(sender);
ASSERT_THAT(RetryEINTR(recvfrom)(
fd().get(), &got_packet, sizeof(got_packet), 0 /* flags */,
fd().get(), &got_packet, sizeof(got_packet), /*flags=*/0,
reinterpret_cast<sockaddr*>(&sender), &sender_len),
SyscallSucceedsWithValue(sizeof(got_packet)));
ASSERT_EQ(sender_len, sizeof(sender));
EXPECT_EQ(memcmp(&sender, &addr, sizeof(addr)), 0);
// The stack should have populated the checksum.
if (IsRunningOnGvisor() && !IsRunningWithHostinet()) {
// TODO(https://github.com/google/gvisor/pull/6957): Use same check as
// Linux.
EXPECT_THAT(got_packet,
FieldsAre(allowed_type, kUnusedICMPCode, 0 /* icmp6_cksum */,
_ /* icmp6_dataun */
));
} else {
EXPECT_THAT(got_packet,
FieldsAre(allowed_type, kUnusedICMPCode,
Not(0) /* icmp6_cksum */, _ /* icmp6_dataun */
));
}
EXPECT_THAT(got_packet,
FieldsAre(allowed_type, kUnusedICMPCode,
/*icmp6_cksum=*/Not(0), /*icmp6_dataun=*/_));
EXPECT_THAT(got_packet.icmp6_data32, ElementsAre(0));
}
}