[netstack] Implement IP(V6)_RECVERR socket option.

PiperOrigin-RevId: 348055514
This commit is contained in:
Ayush Ranjan
2020-12-17 11:10:41 -08:00
committed by gVisor bot
parent 30860902f6
commit 028271b530
9 changed files with 389 additions and 28 deletions
+4 -4
View File
@@ -331,12 +331,12 @@ func (s *socketOpsCommon) GetSockOpt(t *kernel.Task, level int, name int, outPtr
switch level {
case linux.SOL_IP:
switch name {
case linux.IP_TOS, linux.IP_RECVTOS, linux.IP_PKTINFO, linux.IP_RECVORIGDSTADDR:
case linux.IP_TOS, linux.IP_RECVTOS, linux.IP_PKTINFO, linux.IP_RECVORIGDSTADDR, linux.IP_RECVERR:
optlen = sizeofInt32
}
case linux.SOL_IPV6:
switch name {
case linux.IPV6_TCLASS, linux.IPV6_RECVTCLASS, linux.IPV6_V6ONLY, linux.IPV6_RECVORIGDSTADDR:
case linux.IPV6_TCLASS, linux.IPV6_RECVTCLASS, linux.IPV6_RECVERR, linux.IPV6_V6ONLY, linux.IPV6_RECVORIGDSTADDR:
optlen = sizeofInt32
}
case linux.SOL_SOCKET:
@@ -377,14 +377,14 @@ func (s *socketOpsCommon) SetSockOpt(t *kernel.Task, level int, name int, opt []
switch level {
case linux.SOL_IP:
switch name {
case linux.IP_TOS, linux.IP_RECVTOS, linux.IP_RECVORIGDSTADDR:
case linux.IP_TOS, linux.IP_RECVTOS, linux.IP_RECVORIGDSTADDR, linux.IP_RECVERR:
optlen = sizeofInt32
case linux.IP_PKTINFO:
optlen = linux.SizeOfControlMessageIPPacketInfo
}
case linux.SOL_IPV6:
switch name {
case linux.IPV6_TCLASS, linux.IPV6_RECVTCLASS, linux.IPV6_V6ONLY, linux.IPV6_RECVORIGDSTADDR:
case linux.IPV6_TCLASS, linux.IPV6_RECVTCLASS, linux.IPV6_RECVERR, linux.IPV6_V6ONLY, linux.IPV6_RECVORIGDSTADDR:
optlen = sizeofInt32
}
case linux.SOL_SOCKET:
+54 -4
View File
@@ -1405,6 +1405,13 @@ func getSockOptIPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name
v := primitive.Int32(boolToInt32(ep.SocketOptions().GetReceiveTClass()))
return &v, nil
case linux.IPV6_RECVERR:
if outLen < sizeOfInt32 {
return nil, syserr.ErrInvalidArgument
}
v := primitive.Int32(boolToInt32(ep.SocketOptions().GetRecvError()))
return &v, nil
case linux.IPV6_RECVORIGDSTADDR:
if outLen < sizeOfInt32 {
@@ -1579,6 +1586,14 @@ func getSockOptIP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name in
v := primitive.Int32(boolToInt32(ep.SocketOptions().GetReceiveTOS()))
return &v, nil
case linux.IP_RECVERR:
if outLen < sizeOfInt32 {
return nil, syserr.ErrInvalidArgument
}
v := primitive.Int32(boolToInt32(ep.SocketOptions().GetRecvError()))
return &v, nil
case linux.IP_PKTINFO:
if outLen < sizeOfInt32 {
return nil, syserr.ErrInvalidArgument
@@ -2129,6 +2144,16 @@ func setSockOptIPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name
ep.SocketOptions().SetReceiveTClass(v != 0)
return nil
case linux.IPV6_RECVERR:
if len(optVal) == 0 {
return nil
}
v, err := parseIntOrChar(optVal)
if err != nil {
return err
}
ep.SocketOptions().SetRecvError(v != 0)
return nil
case linux.IP6T_SO_SET_REPLACE:
if len(optVal) < linux.SizeOfIP6TReplace {
@@ -2317,6 +2342,17 @@ func setSockOptIP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name in
ep.SocketOptions().SetReceiveTOS(v != 0)
return nil
case linux.IP_RECVERR:
if len(optVal) == 0 {
return nil
}
v, err := parseIntOrChar(optVal)
if err != nil {
return err
}
ep.SocketOptions().SetRecvError(v != 0)
return nil
case linux.IP_PKTINFO:
if len(optVal) == 0 {
return nil
@@ -2386,7 +2422,6 @@ func setSockOptIP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name in
linux.IP_NODEFRAG,
linux.IP_OPTIONS,
linux.IP_PASSSEC,
linux.IP_RECVERR,
linux.IP_RECVFRAGSIZE,
linux.IP_RECVOPTS,
linux.IP_RECVTTL,
@@ -2462,7 +2497,6 @@ func emitUnimplementedEventIPv6(t *kernel.Task, name int) {
linux.IPV6_MULTICAST_IF,
linux.IPV6_MULTICAST_LOOP,
linux.IPV6_RECVDSTOPTS,
linux.IPV6_RECVERR,
linux.IPV6_RECVFRAGSIZE,
linux.IPV6_RECVHOPLIMIT,
linux.IPV6_RECVHOPOPTS,
@@ -2496,7 +2530,6 @@ func emitUnimplementedEventIP(t *kernel.Task, name int) {
linux.IP_PKTINFO,
linux.IP_PKTOPTIONS,
linux.IP_MTU_DISCOVER,
linux.IP_RECVERR,
linux.IP_RECVTTL,
linux.IP_RECVTOS,
linux.IP_MTU,
@@ -2798,6 +2831,23 @@ func (s *socketOpsCommon) updateTimestamp() {
}
}
// dequeueErr is analogous to net/core/skbuff.c:sock_dequeue_err_skb().
func (s *socketOpsCommon) dequeueErr() *tcpip.SockError {
so := s.Endpoint.SocketOptions()
err := so.DequeueErr()
if err == nil {
return nil
}
// Update socket error to reflect ICMP errors in queue.
if nextErr := so.PeekErr(); nextErr != nil && nextErr.ErrOrigin.IsICMPErr() {
so.SetLastError(nextErr.Err)
} else if err.ErrOrigin.IsICMPErr() {
so.SetLastError(nil)
}
return err
}
// addrFamilyFromNetProto returns the address family identifier for the given
// network protocol.
func addrFamilyFromNetProto(net tcpip.NetworkProtocolNumber) int {
@@ -2814,7 +2864,7 @@ func addrFamilyFromNetProto(net tcpip.NetworkProtocolNumber) int {
// recvErr handles MSG_ERRQUEUE for recvmsg(2).
// This is analogous to net/ipv4/ip_sockglue.c:ip_recv_error().
func (s *socketOpsCommon) recvErr(t *kernel.Task, dst usermem.IOSequence) (int, int, linux.SockAddr, uint32, socket.ControlMessages, *syserr.Error) {
sockErr := s.Endpoint.SocketOptions().DequeueErr()
sockErr := s.dequeueErr()
if sockErr == nil {
return 0, 0, nil, 0, socket.ControlMessages{}, syserr.ErrTryAgain
}
+14
View File
@@ -16,6 +16,7 @@ package header
import (
"encoding/binary"
"fmt"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
@@ -213,3 +214,16 @@ func ICMPv4Checksum(h ICMPv4, vv buffer.VectorisedView) uint16 {
return xsum
}
// ICMPOriginFromNetProto returns the appropriate SockErrOrigin to use when
// a packet having a `net` header causing an ICMP error.
func ICMPOriginFromNetProto(net tcpip.NetworkProtocolNumber) tcpip.SockErrOrigin {
switch net {
case IPv4ProtocolNumber:
return tcpip.SockExtErrorOriginICMP
case IPv6ProtocolNumber:
return tcpip.SockExtErrorOriginICMP6
default:
panic(fmt.Sprintf("unsupported net proto to extract ICMP error origin: %d", net))
}
}
+69
View File
@@ -42,6 +42,9 @@ type SocketOptionsHandler interface {
// LastError is invoked when SO_ERROR is read for an endpoint.
LastError() *Error
// UpdateLastError updates the endpoint specific last error field.
UpdateLastError(err *Error)
}
// DefaultSocketOptionsHandler is an embeddable type that implements no-op
@@ -70,6 +73,9 @@ func (*DefaultSocketOptionsHandler) LastError() *Error {
return nil
}
// UpdateLastError implements SocketOptionsHandler.UpdateLastError.
func (*DefaultSocketOptionsHandler) UpdateLastError(*Error) {}
// SocketOptions contains all the variables which store values for SOL_SOCKET,
// SOL_IP, SOL_IPV6 and SOL_TCP level options.
//
@@ -145,6 +151,10 @@ type SocketOptions struct {
// the incoming packet should be returned as an ancillary message.
receiveOriginalDstAddress uint32
// recvErrEnabled determines whether extended reliable error message passing
// is enabled.
recvErrEnabled uint32
// errQueue is the per-socket error queue. It is protected by errQueueMu.
errQueueMu sync.Mutex `state:"nosave"`
errQueue sockErrorList
@@ -171,6 +181,11 @@ func storeAtomicBool(addr *uint32, v bool) {
atomic.StoreUint32(addr, val)
}
// SetLastError sets the last error for a socket.
func (so *SocketOptions) SetLastError(err *Error) {
so.handler.UpdateLastError(err)
}
// GetBroadcast gets value for SO_BROADCAST option.
func (so *SocketOptions) GetBroadcast() bool {
return atomic.LoadUint32(&so.broadcastEnabled) != 0
@@ -338,6 +353,19 @@ func (so *SocketOptions) SetReceiveOriginalDstAddress(v bool) {
storeAtomicBool(&so.receiveOriginalDstAddress, v)
}
// GetRecvError gets value for IP*_RECVERR option.
func (so *SocketOptions) GetRecvError() bool {
return atomic.LoadUint32(&so.recvErrEnabled) != 0
}
// SetRecvError sets value for IP*_RECVERR option.
func (so *SocketOptions) SetRecvError(v bool) {
storeAtomicBool(&so.recvErrEnabled, v)
if !v {
so.pruneErrQueue()
}
}
// GetLastError gets value for SO_ERROR option.
func (so *SocketOptions) GetLastError() *Error {
return so.handler.LastError()
@@ -384,6 +412,11 @@ const (
SockExtErrorOriginICMP6
)
// IsICMPErr indicates if the error originated from an ICMP error.
func (origin SockErrOrigin) IsICMPErr() bool {
return origin == SockExtErrorOriginICMP || origin == SockExtErrorOriginICMP6
}
// SockError represents a queue entry in the per-socket error queue.
//
// +stateify savable
@@ -411,6 +444,13 @@ type SockError struct {
NetProto NetworkProtocolNumber
}
// pruneErrQueue resets the queue.
func (so *SocketOptions) pruneErrQueue() {
so.errQueueMu.Lock()
so.errQueue.Reset()
so.errQueueMu.Unlock()
}
// DequeueErr dequeues a socket extended error from the error queue and returns
// it. Returns nil if queue is empty.
func (so *SocketOptions) DequeueErr() *SockError {
@@ -423,3 +463,32 @@ func (so *SocketOptions) DequeueErr() *SockError {
}
return err
}
// PeekErr returns the error in the front of the error queue. Returns nil if
// the error queue is empty.
func (so *SocketOptions) PeekErr() *SockError {
so.errQueueMu.Lock()
defer so.errQueueMu.Unlock()
return so.errQueue.Front()
}
// QueueErr inserts the error at the back of the error queue.
//
// Preconditions: so.GetRecvError() == true.
func (so *SocketOptions) QueueErr(err *SockError) {
so.errQueueMu.Lock()
defer so.errQueueMu.Unlock()
so.errQueue.PushBack(err)
}
// QueueLocalErr queues a local error onto the local queue.
func (so *SocketOptions) QueueLocalErr(err *Error, net NetworkProtocolNumber, info uint32, dst FullAddress, payload []byte) {
so.QueueErr(&SockError{
Err: err,
ErrOrigin: SockExtErrorOriginLocal,
ErrInfo: info,
Payload: payload,
Dst: dst,
NetProto: net,
})
}
+7
View File
@@ -366,6 +366,13 @@ func (ep *endpoint) LastError() *tcpip.Error {
return err
}
// UpdateLastError implements tcpip.SocketOptionsHandler.UpdateLastError.
func (ep *endpoint) UpdateLastError(err *tcpip.Error) {
ep.lastErrorMu.Lock()
ep.lastError = err
ep.lastErrorMu.Unlock()
}
// GetSockOpt implements tcpip.Endpoint.GetSockOpt.
func (ep *endpoint) GetSockOpt(opt tcpip.GettableSocketOption) *tcpip.Error {
return tcpip.ErrNotSupported
+46 -8
View File
@@ -1303,6 +1303,15 @@ func (e *endpoint) LastError() *tcpip.Error {
return e.lastErrorLocked()
}
// UpdateLastError implements tcpip.SocketOptionsHandler.UpdateLastError.
func (e *endpoint) UpdateLastError(err *tcpip.Error) {
e.LockUser()
e.lastErrorMu.Lock()
e.lastError = err
e.lastErrorMu.Unlock()
e.UnlockUser()
}
// Read reads data from the endpoint.
func (e *endpoint) Read(*tcpip.FullAddress) (buffer.View, tcpip.ControlMessages, *tcpip.Error) {
e.LockUser()
@@ -2708,6 +2717,41 @@ func (e *endpoint) enqueueSegment(s *segment) bool {
return true
}
func (e *endpoint) onICMPError(err *tcpip.Error, id stack.TransportEndpointID, errType byte, errCode byte, extra uint32, pkt *stack.PacketBuffer) {
// Update last error first.
e.lastErrorMu.Lock()
e.lastError = err
e.lastErrorMu.Unlock()
// Update the error queue if IP_RECVERR is enabled.
if e.SocketOptions().GetRecvError() {
e.SocketOptions().QueueErr(&tcpip.SockError{
Err: err,
ErrOrigin: header.ICMPOriginFromNetProto(pkt.NetworkProtocolNumber),
ErrType: errType,
ErrCode: errCode,
ErrInfo: extra,
// Linux passes the payload with the TCP header. We don't know if the TCP
// header even exists, it may not for fragmented packets.
Payload: pkt.Data.ToView(),
Dst: tcpip.FullAddress{
NIC: pkt.NICID,
Addr: id.RemoteAddress,
Port: id.RemotePort,
},
Offender: tcpip.FullAddress{
NIC: pkt.NICID,
Addr: id.LocalAddress,
Port: id.LocalPort,
},
NetProto: pkt.NetworkProtocolNumber,
})
}
// Notify of the error.
e.notifyProtocolGoroutine(notifyError)
}
// HandleControlPacket implements stack.TransportEndpoint.HandleControlPacket.
func (e *endpoint) HandleControlPacket(id stack.TransportEndpointID, typ stack.ControlType, extra uint32, pkt *stack.PacketBuffer) {
switch typ {
@@ -2722,16 +2766,10 @@ func (e *endpoint) HandleControlPacket(id stack.TransportEndpointID, typ stack.C
e.notifyProtocolGoroutine(notifyMTUChanged)
case stack.ControlNoRoute:
e.lastErrorMu.Lock()
e.lastError = tcpip.ErrNoRoute
e.lastErrorMu.Unlock()
e.notifyProtocolGoroutine(notifyError)
e.onICMPError(tcpip.ErrNoRoute, id, byte(header.ICMPv4DstUnreachable), byte(header.ICMPv4HostUnreachable), extra, pkt)
case stack.ControlNetworkUnreachable:
e.lastErrorMu.Lock()
e.lastError = tcpip.ErrNetworkUnreachable
e.lastErrorMu.Unlock()
e.notifyProtocolGoroutine(notifyError)
e.onICMPError(tcpip.ErrNetworkUnreachable, id, byte(header.ICMPv6DstUnreachable), byte(header.ICMPv6NetworkUnreachable), extra, pkt)
}
}
+74 -5
View File
@@ -226,6 +226,13 @@ func (e *endpoint) LastError() *tcpip.Error {
return err
}
// UpdateLastError implements tcpip.SocketOptionsHandler.UpdateLastError.
func (e *endpoint) UpdateLastError(err *tcpip.Error) {
e.lastErrorMu.Lock()
e.lastError = err
e.lastErrorMu.Unlock()
}
// Abort implements stack.TransportEndpoint.Abort.
func (e *endpoint) Abort() {
e.Close()
@@ -511,6 +518,20 @@ func (e *endpoint) write(p tcpip.Payloader, opts tcpip.WriteOptions) (int64, <-c
}
if len(v) > header.UDPMaximumPacketSize {
// Payload can't possibly fit in a packet.
so := e.SocketOptions()
if so.GetRecvError() {
so.QueueLocalErr(
tcpip.ErrMessageTooLong,
route.NetProto,
header.UDPMaximumPacketSize,
tcpip.FullAddress{
NIC: route.NICID(),
Addr: route.RemoteAddress,
Port: dstPort,
},
v,
)
}
return 0, nil, tcpip.ErrMessageTooLong
}
@@ -1338,15 +1359,63 @@ func (e *endpoint) HandlePacket(id stack.TransportEndpointID, pkt *stack.PacketB
}
}
func (e *endpoint) onICMPError(err *tcpip.Error, id stack.TransportEndpointID, errType byte, errCode byte, extra uint32, pkt *stack.PacketBuffer) {
// Update last error first.
e.lastErrorMu.Lock()
e.lastError = err
e.lastErrorMu.Unlock()
// Update the error queue if IP_RECVERR is enabled.
if e.SocketOptions().GetRecvError() {
// Linux passes the payload without the UDP header.
var payload []byte
udp := header.UDP(pkt.Data.ToView())
if len(udp) >= header.UDPMinimumSize {
payload = udp.Payload()
}
e.SocketOptions().QueueErr(&tcpip.SockError{
Err: err,
ErrOrigin: header.ICMPOriginFromNetProto(pkt.NetworkProtocolNumber),
ErrType: errType,
ErrCode: errCode,
ErrInfo: extra,
Payload: payload,
Dst: tcpip.FullAddress{
NIC: pkt.NICID,
Addr: id.RemoteAddress,
Port: id.RemotePort,
},
Offender: tcpip.FullAddress{
NIC: pkt.NICID,
Addr: id.LocalAddress,
Port: id.LocalPort,
},
NetProto: pkt.NetworkProtocolNumber,
})
}
// Notify of the error.
e.waiterQueue.Notify(waiter.EventErr)
}
// HandleControlPacket implements stack.TransportEndpoint.HandleControlPacket.
func (e *endpoint) HandleControlPacket(id stack.TransportEndpointID, typ stack.ControlType, extra uint32, pkt *stack.PacketBuffer) {
if typ == stack.ControlPortUnreachable {
if e.EndpointState() == StateConnected {
e.lastErrorMu.Lock()
e.lastError = tcpip.ErrConnectionRefused
e.lastErrorMu.Unlock()
e.waiterQueue.Notify(waiter.EventErr)
var errType byte
var errCode byte
switch pkt.NetworkProtocolNumber {
case header.IPv4ProtocolNumber:
errType = byte(header.ICMPv4DstUnreachable)
errCode = byte(header.ICMPv4PortUnreachable)
case header.IPv6ProtocolNumber:
errType = byte(header.ICMPv6DstUnreachable)
errCode = byte(header.ICMPv6PortUnreachable)
default:
panic(fmt.Sprintf("unsupported net proto for infering ICMP type and code: %d", pkt.NetworkProtocolNumber))
}
e.onICMPError(tcpip.ErrConnectionRefused, id, errType, errCode, extra, pkt)
return
}
}
+31 -7
View File
@@ -351,6 +351,11 @@ func hostInetFilters() seccomp.SyscallRules {
seccomp.EqualTo(syscall.SOL_IP),
seccomp.EqualTo(syscall.IP_RECVORIGDSTADDR),
},
{
seccomp.MatchAny{},
seccomp.EqualTo(syscall.SOL_IP),
seccomp.EqualTo(syscall.IP_RECVERR),
},
{
seccomp.MatchAny{},
seccomp.EqualTo(syscall.SOL_IPV6),
@@ -361,6 +366,11 @@ func hostInetFilters() seccomp.SyscallRules {
seccomp.EqualTo(syscall.SOL_IPV6),
seccomp.EqualTo(syscall.IPV6_RECVTCLASS),
},
{
seccomp.MatchAny{},
seccomp.EqualTo(syscall.SOL_IPV6),
seccomp.EqualTo(syscall.IPV6_RECVERR),
},
{
seccomp.MatchAny{},
seccomp.EqualTo(syscall.SOL_IPV6),
@@ -444,13 +454,6 @@ func hostInetFilters() seccomp.SyscallRules {
syscall.SYS_SENDMSG: {},
syscall.SYS_SENDTO: {},
syscall.SYS_SETSOCKOPT: []seccomp.Rule{
{
seccomp.MatchAny{},
seccomp.EqualTo(syscall.SOL_IPV6),
seccomp.EqualTo(syscall.IPV6_V6ONLY),
seccomp.MatchAny{},
seccomp.EqualTo(4),
},
{
seccomp.MatchAny{},
seccomp.EqualTo(syscall.SOL_SOCKET),
@@ -521,6 +524,13 @@ func hostInetFilters() seccomp.SyscallRules {
seccomp.MatchAny{},
seccomp.EqualTo(4),
},
{
seccomp.MatchAny{},
seccomp.EqualTo(syscall.SOL_IP),
seccomp.EqualTo(syscall.IP_RECVERR),
seccomp.MatchAny{},
seccomp.EqualTo(4),
},
{
seccomp.MatchAny{},
seccomp.EqualTo(syscall.SOL_IPV6),
@@ -542,6 +552,20 @@ func hostInetFilters() seccomp.SyscallRules {
seccomp.MatchAny{},
seccomp.EqualTo(4),
},
{
seccomp.MatchAny{},
seccomp.EqualTo(syscall.SOL_IPV6),
seccomp.EqualTo(syscall.IPV6_RECVERR),
seccomp.MatchAny{},
seccomp.EqualTo(4),
},
{
seccomp.MatchAny{},
seccomp.EqualTo(syscall.SOL_IPV6),
seccomp.EqualTo(syscall.IPV6_V6ONLY),
seccomp.MatchAny{},
seccomp.EqualTo(4),
},
},
syscall.SYS_SHUTDOWN: []seccomp.Rule{
{
+90
View File
@@ -14,6 +14,8 @@
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/icmp6.h>
#include <netinet/ip_icmp.h>
#include <ctime>
@@ -779,6 +781,94 @@ TEST_P(UdpSocketTest, ConnectAndSendNoReceiver) {
SyscallFailsWithErrno(ECONNREFUSED));
}
#ifdef __linux__
TEST_P(UdpSocketTest, RecvErrorConnRefused) {
// We will simulate an ICMP error and verify that we do receive that error via
// recvmsg(MSG_ERRQUEUE).
ASSERT_NO_ERRNO(BindLoopback());
// Close the socket to release the port so that we get an ICMP error.
ASSERT_THAT(close(bind_.release()), SyscallSucceeds());
// Set IP_RECVERR socket option to enable error queueing.
int v = kSockOptOn;
socklen_t optlen = sizeof(v);
int opt_level = SOL_IP;
int opt_type = IP_RECVERR;
if (GetParam() != AddressFamily::kIpv4) {
opt_level = SOL_IPV6;
opt_type = IPV6_RECVERR;
}
ASSERT_THAT(setsockopt(sock_.get(), opt_level, opt_type, &v, optlen),
SyscallSucceeds());
// Connect to loopback:bind_addr_ which should *hopefully* not be bound by an
// UDP socket. There is no easy way to ensure that the UDP port is not bound
// by another conncurrently running test. *This is potentially flaky*.
const int kBufLen = 300;
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
char buf[kBufLen];
RandomizeBuffer(buf, sizeof(buf));
// Send from sock_ to an unbound port. This should cause ECONNREFUSED.
EXPECT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
SyscallSucceedsWithValue(sizeof(buf)));
// Dequeue error using recvmsg(MSG_ERRQUEUE).
char got[kBufLen];
struct iovec iov;
iov.iov_base = reinterpret_cast<void*>(got);
iov.iov_len = kBufLen;
size_t control_buf_len = CMSG_SPACE(sizeof(sock_extended_err) + addrlen_);
char* control_buf = static_cast<char*>(calloc(1, control_buf_len));
struct sockaddr_storage remote;
memset(&remote, 0, sizeof(remote));
struct msghdr msg = {};
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_flags = 0;
msg.msg_control = control_buf;
msg.msg_controllen = control_buf_len;
msg.msg_name = reinterpret_cast<void*>(&remote);
msg.msg_namelen = addrlen_;
ASSERT_THAT(recvmsg(sock_.get(), &msg, MSG_ERRQUEUE),
SyscallSucceedsWithValue(kBufLen));
// Check the contents of msg.
EXPECT_EQ(memcmp(got, buf, sizeof(buf)), 0); // iovec check
EXPECT_NE(msg.msg_flags & MSG_ERRQUEUE, 0);
EXPECT_EQ(memcmp(&remote, bind_addr_, addrlen_), 0);
// Check the contents of the control message.
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(CMSG_NXTHDR(&msg, cmsg), nullptr);
EXPECT_EQ(cmsg->cmsg_level, opt_level);
EXPECT_EQ(cmsg->cmsg_type, opt_type);
// Check the contents of socket error.
struct sock_extended_err* sock_err =
(struct sock_extended_err*)CMSG_DATA(cmsg);
EXPECT_EQ(sock_err->ee_errno, ECONNREFUSED);
if (GetParam() == AddressFamily::kIpv4) {
EXPECT_EQ(sock_err->ee_origin, SO_EE_ORIGIN_ICMP);
EXPECT_EQ(sock_err->ee_type, ICMP_DEST_UNREACH);
EXPECT_EQ(sock_err->ee_code, ICMP_PORT_UNREACH);
} else {
EXPECT_EQ(sock_err->ee_origin, SO_EE_ORIGIN_ICMP6);
EXPECT_EQ(sock_err->ee_type, ICMP6_DST_UNREACH);
EXPECT_EQ(sock_err->ee_code, ICMP6_DST_UNREACH_NOPORT);
}
// Now verify that the socket error was cleared by recvmsg(MSG_ERRQUEUE).
int err;
optlen = sizeof(err);
ASSERT_THAT(getsockopt(sock_.get(), SOL_SOCKET, SO_ERROR, &err, &optlen),
SyscallSucceeds());
ASSERT_EQ(err, 0);
ASSERT_EQ(optlen, sizeof(err));
}
#endif // __linux__
TEST_P(UdpSocketTest, ZerolengthWriteAllowed) {
// TODO(gvisor.dev/issue/1202): Hostinet does not support zero length writes.
SKIP_IF(IsRunningWithHostinet());