Handle all codes for ICMPv4 destination unreachable message for TCP

Some hosts use iptables and are configured to respond an ICMP destination
unreachable message with a {host/net/admin}prohibited code instead of a RST.
Linux handles these codes correctly by aborting the handshake. This commit
adds handling of all possible codes of an ICMPv4 destination unreachable message
to bring gVisor to parity.

PiperOrigin-RevId: 496703813
This commit is contained in:
Zeling Feng
2022-12-20 11:07:27 -08:00
committed by gVisor bot
parent 2342c658c7
commit 611e6e1247
9 changed files with 323 additions and 43 deletions
+1 -2
View File
@@ -85,7 +85,7 @@ const (
ENODATA
ETIME
ENOSR
_ // Skip for ENOENT = ENONET.
ENONET
ENOPKG
EREMOTE
ENOLINK
@@ -161,7 +161,6 @@ const (
const (
EWOULDBLOCK = EAGAIN
EDEADLOCK = EDEADLK
ENONET = ENOENT
)
// errnos for internal errors.
+5
View File
@@ -54,6 +54,7 @@ var (
ErrInvalidPortRange = New((&tcpip.ErrInvalidPortRange{}).String(), errno.EINVAL)
ErrMulticastInputCannotBeOutput = New((&tcpip.ErrMulticastInputCannotBeOutput{}).String(), errno.EINVAL)
ErrMissingRequiredFields = New((&tcpip.ErrMissingRequiredFields{}).String(), errno.EINVAL)
ErrNoNet = New((&tcpip.ErrNoNet{}).String(), errno.ENONET)
)
// TranslateNetstackError converts an error from the tcpip package to a sentry
@@ -76,6 +77,10 @@ func TranslateNetstackError(err tcpip.Error) *Error {
return ErrDuplicateAddress
case *tcpip.ErrHostUnreachable:
return ErrHostUnreachable
case *tcpip.ErrHostDown:
return ErrHostDown
case *tcpip.ErrNoNet:
return ErrNoNet
case *tcpip.ErrAlreadyBound:
return ErrAlreadyBound
case *tcpip.ErrInvalidEndpointState:
+26
View File
@@ -394,6 +394,32 @@ func (*ErrHostUnreachable) IgnoreStats() bool {
}
func (*ErrHostUnreachable) String() string { return "no route to host" }
// ErrHostDown indicates that a destination host is down.
//
// +stateify savable
type ErrHostDown struct{}
func (*ErrHostDown) isError() {}
// IgnoreStats implements Error.
func (*ErrHostDown) IgnoreStats() bool {
return false
}
func (*ErrHostDown) String() string { return "host is down" }
// ErrNoNet indicates that the host is not on the network.
//
// +stateify savable
type ErrNoNet struct{}
func (*ErrNoNet) isError() {}
// IgnoreStats implements Error.
func (*ErrNoNet) IgnoreStats() bool {
return false
}
func (*ErrNoNet) String() string { return "machine is not on the network" }
// ErrNoSuchFile is used to indicate that ENOENT should be returned the to
// calling application.
//
+16 -8
View File
@@ -106,14 +106,22 @@ const (
// ICMP codes for ICMPv4 Destination Unreachable messages as defined in RFC 792,
// RFC 1122 section 3.2.2.1 and RFC 1812 section 5.2.7.1.
const (
ICMPv4NetUnreachable ICMPv4Code = 0
ICMPv4HostUnreachable ICMPv4Code = 1
ICMPv4ProtoUnreachable ICMPv4Code = 2
ICMPv4PortUnreachable ICMPv4Code = 3
ICMPv4FragmentationNeeded ICMPv4Code = 4
ICMPv4NetProhibited ICMPv4Code = 9
ICMPv4HostProhibited ICMPv4Code = 10
ICMPv4AdminProhibited ICMPv4Code = 13
ICMPv4NetUnreachable ICMPv4Code = 0
ICMPv4HostUnreachable ICMPv4Code = 1
ICMPv4ProtoUnreachable ICMPv4Code = 2
ICMPv4PortUnreachable ICMPv4Code = 3
ICMPv4FragmentationNeeded ICMPv4Code = 4
ICMPv4SourceRouteFailed ICMPv4Code = 5
ICMPv4DestinationNetworkUnknown ICMPv4Code = 6
ICMPv4DestinationHostUnknown ICMPv4Code = 7
ICMPv4SourceHostIsolated ICMPv4Code = 8
ICMPv4NetProhibited ICMPv4Code = 9
ICMPv4HostProhibited ICMPv4Code = 10
ICMPv4NetUnreachableForTos ICMPv4Code = 11
ICMPv4HostUnreachableForTos ICMPv4Code = 12
ICMPv4AdminProhibited ICMPv4Code = 13
ICMPv4HostPrecedenceViolation ICMPv4Code = 14
ICMPv4PrecedenceCutInEffect ICMPv4Code = 15
)
// ICMPv4UnusedCode is a code to use in ICMP messages where no code is needed.
+124 -1
View File
@@ -68,6 +68,28 @@ func (*icmpv4DestinationHostUnreachableSockError) Kind() stack.TransportErrorKin
return stack.DestinationHostUnreachableTransportError
}
var _ stack.TransportError = (*icmpv4DestinationNetUnreachableSockError)(nil)
// icmpv4DestinationNetUnreachableSockError is an ICMPv4 Destination Net
// Unreachable error.
//
// It indicates that a packet was not able to reach the destination network.
//
// +stateify savable
type icmpv4DestinationNetUnreachableSockError struct {
icmpv4DestinationUnreachableSockError
}
// Code implements tcpip.SockErrorCause.
func (*icmpv4DestinationNetUnreachableSockError) Code() uint8 {
return uint8(header.ICMPv4NetUnreachable)
}
// Kind implements stack.TransportError.
func (*icmpv4DestinationNetUnreachableSockError) Kind() stack.TransportErrorKind {
return stack.DestinationNetworkUnreachableTransportError
}
var _ stack.TransportError = (*icmpv4DestinationPortUnreachableSockError)(nil)
// icmpv4DestinationPortUnreachableSockError is an ICMPv4 Destination Port
@@ -91,6 +113,89 @@ func (*icmpv4DestinationPortUnreachableSockError) Kind() stack.TransportErrorKin
return stack.DestinationPortUnreachableTransportError
}
var _ stack.TransportError = (*icmpv4DestinationProtoUnreachableSockError)(nil)
// icmpv4DestinationProtoUnreachableSockError is an ICMPv4 Destination Protocol
// Unreachable error.
//
// It indicates that a packet reached the destination host, but the transport
// protocol was not reachable
//
// +stateify savable
type icmpv4DestinationProtoUnreachableSockError struct {
icmpv4DestinationUnreachableSockError
}
// Code implements tcpip.SockErrorCause.
func (*icmpv4DestinationProtoUnreachableSockError) Code() uint8 {
return uint8(header.ICMPv4ProtoUnreachable)
}
// Kind implements stack.TransportError.
func (*icmpv4DestinationProtoUnreachableSockError) Kind() stack.TransportErrorKind {
return stack.DestinationProtoUnreachableTransportError
}
var _ stack.TransportError = (*icmpv4SourceRouteFailedSockError)(nil)
// icmpv4SourceRouteFailedSockError is an ICMPv4 Destination Unreachable error
// due to source route failed.
//
// +stateify savable
type icmpv4SourceRouteFailedSockError struct {
icmpv4DestinationUnreachableSockError
}
// Code implements tcpip.SockErrorCause.
func (*icmpv4SourceRouteFailedSockError) Code() uint8 {
return uint8(header.ICMPv4SourceRouteFailed)
}
// Kind implements stack.TransportError.
func (*icmpv4SourceRouteFailedSockError) Kind() stack.TransportErrorKind {
return stack.SourceRouteFailedTransportError
}
var _ stack.TransportError = (*icmpv4SourceHostIsolatedSockError)(nil)
// icmpv4SourceHostIsolatedSockError is an ICMPv4 Destination Unreachable error
// due to source host isolated (not on the network).
//
// +stateify savable
type icmpv4SourceHostIsolatedSockError struct {
icmpv4DestinationUnreachableSockError
}
// Code implements tcpip.SockErrorCause.
func (*icmpv4SourceHostIsolatedSockError) Code() uint8 {
return uint8(header.ICMPv4SourceHostIsolated)
}
// Kind implements stack.TransportError.
func (*icmpv4SourceHostIsolatedSockError) Kind() stack.TransportErrorKind {
return stack.SourceHostIsolatedTransportError
}
var _ stack.TransportError = (*icmpv4DestinationHostUnknownSockError)(nil)
// icmpv4DestinationHostUnknownSockError is an ICMPv4 Destination Unreachable
// error due to destination host unknown/down.
//
// +stateify savable
type icmpv4DestinationHostUnknownSockError struct {
icmpv4DestinationUnreachableSockError
}
// Code implements tcpip.SockErrorCause.
func (*icmpv4DestinationHostUnknownSockError) Code() uint8 {
return uint8(header.ICMPv4DestinationHostUnknown)
}
// Kind implements stack.TransportError.
func (*icmpv4DestinationHostUnknownSockError) Kind() stack.TransportErrorKind {
return stack.DestinationHostDownTransportError
}
var _ stack.TransportError = (*icmpv4FragmentationNeededSockError)(nil)
// icmpv4FragmentationNeededSockError is an ICMPv4 Destination Unreachable error
@@ -362,7 +467,17 @@ func (e *endpoint) handleICMP(pkt stack.PacketBufferPtr) {
mtu := h.MTU()
code := h.Code()
switch code {
case header.ICMPv4HostUnreachable:
case header.ICMPv4NetUnreachable,
header.ICMPv4DestinationNetworkUnknown,
header.ICMPv4NetUnreachableForTos,
header.ICMPv4NetProhibited:
e.handleControl(&icmpv4DestinationNetUnreachableSockError{}, pkt)
case header.ICMPv4HostUnreachable,
header.ICMPv4HostProhibited,
header.ICMPv4AdminProhibited,
header.ICMPv4HostUnreachableForTos,
header.ICMPv4HostPrecedenceViolation,
header.ICMPv4PrecedenceCutInEffect:
e.handleControl(&icmpv4DestinationHostUnreachableSockError{}, pkt)
case header.ICMPv4PortUnreachable:
e.handleControl(&icmpv4DestinationPortUnreachableSockError{}, pkt)
@@ -372,6 +487,14 @@ func (e *endpoint) handleICMP(pkt stack.PacketBufferPtr) {
networkMTU = 0
}
e.handleControl(&icmpv4FragmentationNeededSockError{mtu: networkMTU}, pkt)
case header.ICMPv4ProtoUnreachable:
e.handleControl(&icmpv4DestinationProtoUnreachableSockError{}, pkt)
case header.ICMPv4SourceRouteFailed:
e.handleControl(&icmpv4SourceRouteFailedSockError{}, pkt)
case header.ICMPv4SourceHostIsolated:
e.handleControl(&icmpv4SourceHostIsolatedSockError{}, pkt)
case header.ICMPv4DestinationHostUnknown:
e.handleControl(&icmpv4DestinationHostUnknownSockError{}, pkt)
}
case header.ICMPv4SrcQuench:
received.srcQuench.Increment()
+15
View File
@@ -84,6 +84,21 @@ const (
// DestinationNetworkUnreachableTransportError indicates that the destination
// network was unreachable.
DestinationNetworkUnreachableTransportError
// DestinationProtoUnreachableTransportError indicates that the destination
// protocol was unreachable.
DestinationProtoUnreachableTransportError
// SourceRouteFailedTransportError indicates that the source route failed.
SourceRouteFailedTransportError
// SourceHostIsolatedTransportError indicates that the source machine is not
// on the network.
SourceHostIsolatedTransportError
// DestinationHostDownTransportError indicates that the destination host is
// down.
DestinationHostDownTransportError
)
// TransportError is a marker interface for errors that may be handled by the
+7 -1
View File
@@ -128,9 +128,15 @@ func maybeFailTimerHandler(e *endpoint, f func() tcpip.Error) func() {
e.mu.Lock()
if err := f(); err != nil {
e.lastErrorMu.Lock()
// If the handler timed out and we have a lastError recorded (maybe due
// to an ICMP message received), promote it to be the hard error.
if _, isTimeout := err.(*tcpip.ErrTimeout); e.lastError != nil && isTimeout {
e.hardError = e.lastError
} else {
e.hardError = err
}
e.lastError = err
e.lastErrorMu.Unlock()
e.hardError = err
e.cleanupLocked()
e.setEndpointState(StateError)
e.mu.Unlock()
+10
View File
@@ -2909,6 +2909,16 @@ func (e *endpoint) HandleError(transErr stack.TransportError, pkt stack.PacketBu
e.onICMPError(&tcpip.ErrHostUnreachable{}, transErr, pkt)
case stack.DestinationNetworkUnreachableTransportError:
e.onICMPError(&tcpip.ErrNetworkUnreachable{}, transErr, pkt)
case stack.DestinationPortUnreachableTransportError:
e.onICMPError(&tcpip.ErrConnectionRefused{}, transErr, pkt)
case stack.DestinationProtoUnreachableTransportError:
e.onICMPError(&tcpip.ErrUnknownProtocolOption{}, transErr, pkt)
case stack.SourceRouteFailedTransportError:
e.onICMPError(&tcpip.ErrNotSupported{}, transErr, pkt)
case stack.SourceHostIsolatedTransportError:
e.onICMPError(&tcpip.ErrNoNet{}, transErr, pkt)
case stack.DestinationHostDownTransportError:
e.onICMPError(&tcpip.ErrHostDown{}, transErr, pkt)
}
}
@@ -33,43 +33,131 @@ func init() {
// an ICMP destination unreachable message is sent in response to the inital
// SYN.
func TestTCPSynSentUnreachable(t *testing.T) {
// Create the DUT and connection.
dut := testbench.NewDUT(t)
clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4)
port := uint16(9001)
conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port})
defer conn.Close(t)
for _, tt := range []struct {
desc string
code header.ICMPv4Code
wantErrno unix.Errno
}{
{desc: "net_unreachable", code: header.ICMPv4NetUnreachable, wantErrno: unix.ENETUNREACH},
{desc: "host_unreachable", code: header.ICMPv4HostUnreachable, wantErrno: unix.EHOSTUNREACH},
{desc: "proto_unreachable", code: header.ICMPv4ProtoUnreachable, wantErrno: unix.ENOPROTOOPT},
{desc: "port_unreachable", code: header.ICMPv4PortUnreachable, wantErrno: unix.ECONNREFUSED},
{desc: "source_route_failed", code: header.ICMPv4SourceRouteFailed, wantErrno: unix.EOPNOTSUPP},
{desc: "dest_net_unknown", code: header.ICMPv4DestinationNetworkUnknown, wantErrno: unix.ENETUNREACH},
{desc: "dest_host_unknown", code: header.ICMPv4DestinationHostUnknown, wantErrno: unix.EHOSTDOWN},
{desc: "src_host_isolated", code: header.ICMPv4SourceHostIsolated, wantErrno: unix.ENONET},
{desc: "net_prohibited", code: header.ICMPv4NetProhibited, wantErrno: unix.ENETUNREACH},
{desc: "host_prohibited", code: header.ICMPv4HostProhibited, wantErrno: unix.EHOSTUNREACH},
{desc: "net_unreachable_tos", code: header.ICMPv4NetUnreachableForTos, wantErrno: unix.ENETUNREACH},
{desc: "host_unreachable_tos", code: header.ICMPv4HostUnreachableForTos, wantErrno: unix.EHOSTUNREACH},
{desc: "admin_prohibited", code: header.ICMPv4AdminProhibited, wantErrno: unix.EHOSTUNREACH},
{desc: "precedence_violation", code: header.ICMPv4HostPrecedenceViolation, wantErrno: unix.EHOSTUNREACH},
{desc: "precedence_cut", code: header.ICMPv4PrecedenceCutInEffect, wantErrno: unix.EHOSTUNREACH},
} {
t.Run(tt.desc, func(t *testing.T) {
// Create the DUT and connection.
dut := testbench.NewDUT(t)
clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4)
port := uint16(9001)
conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port})
defer conn.Close(t)
// Bring the DUT to SYN-SENT state with a non-blocking connect.
sa := unix.SockaddrInet4{Port: int(port)}
copy(sa.Addr[:], dut.Net.LocalIPv4)
if _, err := dut.ConnectWithErrno(context.Background(), t, clientFD, &sa); err != unix.EINPROGRESS {
t.Errorf("got connect() = %v, want EINPROGRESS", err)
sa := unix.SockaddrInet4{Port: int(port)}
copy(sa.Addr[:], dut.Net.LocalIPv4)
// Bring the DUT to SYN-SENT state with a non-blocking connect.
if _, err := dut.ConnectWithErrno(context.Background(), t, clientFD, &sa); err != unix.EINPROGRESS {
t.Errorf("got connect() = %v, want EINPROGRESS", err)
}
// Get the SYN.
tcp, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second)
if err != nil {
t.Fatalf("expected SYN: %s", err)
}
// Send a host unreachable message.
icmpPayload := testbench.Layers{tcp.Prev(), tcp}
bytes, err := icmpPayload.ToBytes()
if err != nil {
t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err)
}
layers := conn.CreateFrame(t, nil)
layers[len(layers)-1] = &testbench.ICMPv4{
Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
Code: testbench.ICMPv4Code(tt.code),
Payload: bytes,
}
conn.SendFrameStateless(t, layers)
if err := getConnectError(t, &dut, clientFD); err != tt.wantErrno {
t.Errorf("got connect() = %s(%d), want %s(%d)", err, err, tt.wantErrno, tt.wantErrno)
}
})
}
}
// Get the SYN.
tcp, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second)
if err != nil {
t.Fatalf("expected SYN: %s", err)
}
func TestTCPEstablishedUnreachable(t *testing.T) {
for _, tt := range []struct {
desc string
code header.ICMPv4Code
wantErrno unix.Errno
}{
{desc: "net_unreachable", code: header.ICMPv4NetUnreachable, wantErrno: unix.ENETUNREACH},
{desc: "host_unreachable", code: header.ICMPv4HostUnreachable, wantErrno: unix.EHOSTUNREACH},
{desc: "proto_unreachable", code: header.ICMPv4ProtoUnreachable, wantErrno: unix.ENOPROTOOPT},
{desc: "port_unreachable", code: header.ICMPv4PortUnreachable, wantErrno: unix.ECONNREFUSED},
{desc: "source_route_failed", code: header.ICMPv4SourceRouteFailed, wantErrno: unix.EOPNOTSUPP},
{desc: "dest_net_unknown", code: header.ICMPv4DestinationNetworkUnknown, wantErrno: unix.ENETUNREACH},
{desc: "dest_host_unknown", code: header.ICMPv4DestinationHostUnknown, wantErrno: unix.EHOSTDOWN},
{desc: "src_host_isolated", code: header.ICMPv4SourceHostIsolated, wantErrno: unix.ENONET},
{desc: "net_prohibited", code: header.ICMPv4NetProhibited, wantErrno: unix.ENETUNREACH},
{desc: "host_prohibited", code: header.ICMPv4HostProhibited, wantErrno: unix.EHOSTUNREACH},
{desc: "net_unreachable_tos", code: header.ICMPv4NetUnreachableForTos, wantErrno: unix.ENETUNREACH},
{desc: "host_unreachable_tos", code: header.ICMPv4HostUnreachableForTos, wantErrno: unix.EHOSTUNREACH},
{desc: "admin_prohibited", code: header.ICMPv4AdminProhibited, wantErrno: unix.EHOSTUNREACH},
{desc: "precedence_violation", code: header.ICMPv4HostPrecedenceViolation, wantErrno: unix.EHOSTUNREACH},
{desc: "precedence_cut", code: header.ICMPv4PrecedenceCutInEffect, wantErrno: unix.EHOSTUNREACH},
} {
t.Run(tt.desc, func(t *testing.T) {
dut := testbench.NewDUT(t)
listenerFd, listenerPort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
defer dut.Close(t, listenerFd)
conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &listenerPort}, testbench.TCP{SrcPort: &listenerPort})
defer conn.Close(t)
conn.Connect(t)
acceptedFd, addr := dut.Accept(t, listenerFd)
_ = addr
// Send a host unreachable message.
icmpPayload := testbench.Layers{tcp.Prev(), tcp}
bytes, err := icmpPayload.ToBytes()
if err != nil {
t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err)
}
dut.SetSockOptInt(t, acceptedFd, unix.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, 50)
dut.Send(t, acceptedFd, []byte("Hello"), 0)
layers := conn.CreateFrame(t, nil)
layers[len(layers)-1] = &testbench.ICMPv4{
Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable),
Payload: bytes,
}
conn.SendFrameStateless(t, layers)
if received, err := conn.ExpectData(t, &testbench.TCP{}, &testbench.Payload{Bytes: []byte("Hello")}, time.Second); err != nil {
t.Fatalf("Expected data from DUT, got none: %s", err)
} else {
// Send a host unreachable message.
icmpPayload := received[len(received)-3 : len(received)-1]
bytes, err := icmpPayload.ToBytes()
if err != nil {
t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err)
}
if err := getConnectError(t, &dut, clientFD); err != unix.EHOSTUNREACH {
t.Errorf("got connect() = %v, want EHOSTUNREACH", err)
layers := conn.CreateFrame(t, nil)
layers[len(layers)-1] = &testbench.ICMPv4{
Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
Code: testbench.ICMPv4Code(tt.code),
Payload: bytes,
}
conn.SendFrameStateless(t, layers)
}
dut.Send(t, acceptedFd, []byte("Hello"), 0)
time.Sleep(500 * time.Millisecond)
if _, err := dut.SendWithErrno(context.Background(), t, acceptedFd, []byte("Hello"), 0); err != tt.wantErrno {
t.Fatalf("got send() = %s(%d), want %s(%d)", err, err, tt.wantErrno, tt.wantErrno)
}
})
}
}