mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Wake up any waiters on an ICMP error on UDP socket.
This change wakes up any waiters when we receive an ICMP port unreachable control packet on an UDP socket as well as sets waiter.EventErr in the result returned by Readiness() when e.lastError is not nil. The latter is required where an epoll()/poll() is done after the error is already handled since we will never notify again in such cases. PiperOrigin-RevId: 339370469
This commit is contained in:
committed by
gVisor bot
parent
1c2836da37
commit
24c33de748
@@ -1369,6 +1369,12 @@ func (e *endpoint) Readiness(mask waiter.EventMask) waiter.EventMask {
|
||||
e.rcvMu.Unlock()
|
||||
}
|
||||
|
||||
e.lastErrorMu.Lock()
|
||||
hasError := e.lastError != nil
|
||||
e.lastErrorMu.Unlock()
|
||||
if hasError {
|
||||
result |= waiter.EventErr
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1468,14 +1474,16 @@ func (e *endpoint) HandlePacket(r *stack.Route, id stack.TransportEndpointID, pk
|
||||
func (e *endpoint) HandleControlPacket(id stack.TransportEndpointID, typ stack.ControlType, extra uint32, pkt *stack.PacketBuffer) {
|
||||
if typ == stack.ControlPortUnreachable {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
|
||||
if e.state == StateConnected {
|
||||
e.lastErrorMu.Lock()
|
||||
defer e.lastErrorMu.Unlock()
|
||||
|
||||
e.lastError = tcpip.ErrConnectionRefused
|
||||
e.lastErrorMu.Unlock()
|
||||
e.mu.RUnlock()
|
||||
|
||||
e.waiterQueue.Notify(waiter.EventErr)
|
||||
return
|
||||
}
|
||||
e.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -679,6 +679,43 @@ TEST_P(UdpSocketTest, SendToAddressOtherThanConnected) {
|
||||
SyscallSucceedsWithValue(sizeof(buf)));
|
||||
}
|
||||
|
||||
TEST_P(UdpSocketTest, ConnectAndSendNoReceiver) {
|
||||
ASSERT_NO_ERRNO(BindLoopback());
|
||||
// Close the socket to release the port so that we get an ICMP error.
|
||||
ASSERT_THAT(close(bind_.release()), 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*.
|
||||
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
||||
|
||||
char buf[512];
|
||||
EXPECT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
|
||||
SyscallSucceedsWithValue(sizeof(buf)));
|
||||
|
||||
constexpr int kTimeout = 1000;
|
||||
// Poll to make sure we get the ICMP error back before issuing more writes.
|
||||
struct pollfd pfd = {sock_.get(), POLLERR, 0};
|
||||
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
|
||||
|
||||
// Next write should fail with ECONNREFUSED due to the ICMP error generated in
|
||||
// response to the previous write.
|
||||
ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
|
||||
SyscallFailsWithErrno(ECONNREFUSED));
|
||||
|
||||
// The next write should succeed again since the last write call would have
|
||||
// retrieved and cleared the socket error.
|
||||
ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0), SyscallSucceeds());
|
||||
|
||||
// Poll to make sure we get the ICMP error back before issuing more writes.
|
||||
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
|
||||
|
||||
// Next write should fail with ECONNREFUSED due to the ICMP error generated in
|
||||
// response to the previous write.
|
||||
ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
|
||||
SyscallFailsWithErrno(ECONNREFUSED));
|
||||
}
|
||||
|
||||
TEST_P(UdpSocketTest, ZerolengthWriteAllowed) {
|
||||
// TODO(gvisor.dev/issue/1202): Hostinet does not support zero length writes.
|
||||
SKIP_IF(IsRunningWithHostinet());
|
||||
|
||||
Reference in New Issue
Block a user