mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Support SOL_IPV6 -> IPV6_CHECKSUM
PiperOrigin-RevId: 419164074
This commit is contained in:
committed by
gVisor bot
parent
58b9bdfc21
commit
b488df0a2f
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user