From 611e6e1247a0691f5fd198f411c68b3bc79d90af Mon Sep 17 00:00:00 2001 From: Zeling Feng Date: Tue, 20 Dec 2022 11:05:41 -0800 Subject: [PATCH] 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 --- pkg/abi/linux/errno/errno.go | 3 +- pkg/syserr/netstack.go | 5 + pkg/tcpip/errors.go | 26 +++ pkg/tcpip/header/icmpv4.go | 24 ++- pkg/tcpip/network/ipv4/icmp.go | 125 ++++++++++++++- pkg/tcpip/stack/registration.go | 15 ++ pkg/tcpip/transport/tcp/connect.go | 8 +- pkg/tcpip/transport/tcp/endpoint.go | 10 ++ .../tests/tcp_network_unreachable_test.go | 150 ++++++++++++++---- 9 files changed, 323 insertions(+), 43 deletions(-) diff --git a/pkg/abi/linux/errno/errno.go b/pkg/abi/linux/errno/errno.go index 38ebbb1d7..59fb25187 100644 --- a/pkg/abi/linux/errno/errno.go +++ b/pkg/abi/linux/errno/errno.go @@ -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. diff --git a/pkg/syserr/netstack.go b/pkg/syserr/netstack.go index 6f1797045..3928106da 100644 --- a/pkg/syserr/netstack.go +++ b/pkg/syserr/netstack.go @@ -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: diff --git a/pkg/tcpip/errors.go b/pkg/tcpip/errors.go index ff0a7be4f..78cc9fdd4 100644 --- a/pkg/tcpip/errors.go +++ b/pkg/tcpip/errors.go @@ -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. // diff --git a/pkg/tcpip/header/icmpv4.go b/pkg/tcpip/header/icmpv4.go index dd385b455..abb64db3b 100644 --- a/pkg/tcpip/header/icmpv4.go +++ b/pkg/tcpip/header/icmpv4.go @@ -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. diff --git a/pkg/tcpip/network/ipv4/icmp.go b/pkg/tcpip/network/ipv4/icmp.go index 7823f991a..4843f60ca 100644 --- a/pkg/tcpip/network/ipv4/icmp.go +++ b/pkg/tcpip/network/ipv4/icmp.go @@ -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() diff --git a/pkg/tcpip/stack/registration.go b/pkg/tcpip/stack/registration.go index cf3bfe3b2..e06abbcda 100644 --- a/pkg/tcpip/stack/registration.go +++ b/pkg/tcpip/stack/registration.go @@ -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 diff --git a/pkg/tcpip/transport/tcp/connect.go b/pkg/tcpip/transport/tcp/connect.go index 451f2cef1..ae5eeb667 100644 --- a/pkg/tcpip/transport/tcp/connect.go +++ b/pkg/tcpip/transport/tcp/connect.go @@ -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() diff --git a/pkg/tcpip/transport/tcp/endpoint.go b/pkg/tcpip/transport/tcp/endpoint.go index 613c6c43e..20419ee0f 100644 --- a/pkg/tcpip/transport/tcp/endpoint.go +++ b/pkg/tcpip/transport/tcp/endpoint.go @@ -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) } } diff --git a/test/packetimpact/tests/tcp_network_unreachable_test.go b/test/packetimpact/tests/tcp_network_unreachable_test.go index e92e6aa9b..61e0ab8b5 100644 --- a/test/packetimpact/tests/tcp_network_unreachable_test.go +++ b/test/packetimpact/tests/tcp_network_unreachable_test.go @@ -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) + } + }) } }