gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/packetimpact/tests/tcp_network_unreachable_test.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package tcp_synsent_reset_test 16 17 import ( 18 "context" 19 "flag" 20 "testing" 21 "time" 22 23 "golang.org/x/sys/unix" 24 "gvisor.dev/gvisor/pkg/tcpip/header" 25 "gvisor.dev/gvisor/test/packetimpact/testbench" 26 ) 27 28 func init() { 29 testbench.Initialize(flag.CommandLine) 30 } 31 32 // TestTCPSynSentUnreachable verifies that TCP connections fail immediately when 33 // an ICMP destination unreachable message is sent in response to the inital 34 // SYN. 35 func TestTCPSynSentUnreachable(t *testing.T) { 36 for _, tt := range []struct { 37 desc string 38 code header.ICMPv4Code 39 wantErrno unix.Errno 40 }{ 41 {desc: "net_unreachable", code: header.ICMPv4NetUnreachable, wantErrno: unix.ENETUNREACH}, 42 {desc: "host_unreachable", code: header.ICMPv4HostUnreachable, wantErrno: unix.EHOSTUNREACH}, 43 {desc: "proto_unreachable", code: header.ICMPv4ProtoUnreachable, wantErrno: unix.ENOPROTOOPT}, 44 {desc: "port_unreachable", code: header.ICMPv4PortUnreachable, wantErrno: unix.ECONNREFUSED}, 45 {desc: "source_route_failed", code: header.ICMPv4SourceRouteFailed, wantErrno: unix.EOPNOTSUPP}, 46 {desc: "dest_net_unknown", code: header.ICMPv4DestinationNetworkUnknown, wantErrno: unix.ENETUNREACH}, 47 {desc: "dest_host_unknown", code: header.ICMPv4DestinationHostUnknown, wantErrno: unix.EHOSTDOWN}, 48 {desc: "src_host_isolated", code: header.ICMPv4SourceHostIsolated, wantErrno: unix.ENONET}, 49 {desc: "net_prohibited", code: header.ICMPv4NetProhibited, wantErrno: unix.ENETUNREACH}, 50 {desc: "host_prohibited", code: header.ICMPv4HostProhibited, wantErrno: unix.EHOSTUNREACH}, 51 {desc: "net_unreachable_tos", code: header.ICMPv4NetUnreachableForTos, wantErrno: unix.ENETUNREACH}, 52 {desc: "host_unreachable_tos", code: header.ICMPv4HostUnreachableForTos, wantErrno: unix.EHOSTUNREACH}, 53 {desc: "admin_prohibited", code: header.ICMPv4AdminProhibited, wantErrno: unix.EHOSTUNREACH}, 54 {desc: "precedence_violation", code: header.ICMPv4HostPrecedenceViolation, wantErrno: unix.EHOSTUNREACH}, 55 {desc: "precedence_cut", code: header.ICMPv4PrecedenceCutInEffect, wantErrno: unix.EHOSTUNREACH}, 56 } { 57 t.Run(tt.desc, func(t *testing.T) { 58 // Create the DUT and connection. 59 dut := testbench.NewDUT(t) 60 clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) 61 port := uint16(9001) 62 conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port}) 63 defer conn.Close(t) 64 65 sa := unix.SockaddrInet4{Port: int(port)} 66 copy(sa.Addr[:], dut.Net.LocalIPv4) 67 // Bring the DUT to SYN-SENT state with a non-blocking connect. 68 if _, err := dut.ConnectWithErrno(context.Background(), t, clientFD, &sa); err != unix.EINPROGRESS { 69 t.Errorf("got connect() = %v, want EINPROGRESS", err) 70 } 71 72 // Get the SYN. 73 tcp, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second) 74 if err != nil { 75 t.Fatalf("expected SYN: %s", err) 76 } 77 78 // Send a host unreachable message. 79 icmpPayload := testbench.Layers{tcp.Prev(), tcp} 80 bytes, err := icmpPayload.ToBytes() 81 if err != nil { 82 t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err) 83 } 84 85 layers := conn.CreateFrame(t, nil) 86 layers[len(layers)-1] = &testbench.ICMPv4{ 87 Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable), 88 Code: testbench.ICMPv4Code(tt.code), 89 Payload: bytes, 90 } 91 conn.SendFrameStateless(t, layers) 92 93 if err := getConnectError(t, &dut, clientFD); err != tt.wantErrno { 94 t.Errorf("got connect() = %s(%d), want %s(%d)", err, err, tt.wantErrno, tt.wantErrno) 95 } 96 }) 97 } 98 } 99 100 func TestTCPEstablishedUnreachable(t *testing.T) { 101 for _, tt := range []struct { 102 desc string 103 code header.ICMPv4Code 104 wantErrno unix.Errno 105 }{ 106 {desc: "net_unreachable", code: header.ICMPv4NetUnreachable, wantErrno: unix.ENETUNREACH}, 107 {desc: "host_unreachable", code: header.ICMPv4HostUnreachable, wantErrno: unix.EHOSTUNREACH}, 108 {desc: "proto_unreachable", code: header.ICMPv4ProtoUnreachable, wantErrno: unix.ENOPROTOOPT}, 109 {desc: "port_unreachable", code: header.ICMPv4PortUnreachable, wantErrno: unix.ECONNREFUSED}, 110 {desc: "source_route_failed", code: header.ICMPv4SourceRouteFailed, wantErrno: unix.EOPNOTSUPP}, 111 {desc: "dest_net_unknown", code: header.ICMPv4DestinationNetworkUnknown, wantErrno: unix.ENETUNREACH}, 112 {desc: "dest_host_unknown", code: header.ICMPv4DestinationHostUnknown, wantErrno: unix.EHOSTDOWN}, 113 {desc: "src_host_isolated", code: header.ICMPv4SourceHostIsolated, wantErrno: unix.ENONET}, 114 {desc: "net_prohibited", code: header.ICMPv4NetProhibited, wantErrno: unix.ENETUNREACH}, 115 {desc: "host_prohibited", code: header.ICMPv4HostProhibited, wantErrno: unix.EHOSTUNREACH}, 116 {desc: "net_unreachable_tos", code: header.ICMPv4NetUnreachableForTos, wantErrno: unix.ENETUNREACH}, 117 {desc: "host_unreachable_tos", code: header.ICMPv4HostUnreachableForTos, wantErrno: unix.EHOSTUNREACH}, 118 {desc: "admin_prohibited", code: header.ICMPv4AdminProhibited, wantErrno: unix.EHOSTUNREACH}, 119 {desc: "precedence_violation", code: header.ICMPv4HostPrecedenceViolation, wantErrno: unix.EHOSTUNREACH}, 120 {desc: "precedence_cut", code: header.ICMPv4PrecedenceCutInEffect, wantErrno: unix.EHOSTUNREACH}, 121 } { 122 t.Run(tt.desc, func(t *testing.T) { 123 dut := testbench.NewDUT(t) 124 listenerFd, listenerPort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) 125 defer dut.Close(t, listenerFd) 126 conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &listenerPort}, testbench.TCP{SrcPort: &listenerPort}) 127 defer conn.Close(t) 128 conn.Connect(t) 129 acceptedFd, addr := dut.Accept(t, listenerFd) 130 _ = addr 131 132 dut.SetSockOptInt(t, acceptedFd, unix.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, 50) 133 dut.Send(t, acceptedFd, []byte("Hello"), 0) 134 135 if received, err := conn.ExpectData(t, &testbench.TCP{}, &testbench.Payload{Bytes: []byte("Hello")}, time.Second); err != nil { 136 t.Fatalf("Expected data from DUT, got none: %s", err) 137 } else { 138 // Send a host unreachable message. 139 icmpPayload := received[len(received)-3 : len(received)-1] 140 bytes, err := icmpPayload.ToBytes() 141 if err != nil { 142 t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err) 143 } 144 145 layers := conn.CreateFrame(t, nil) 146 layers[len(layers)-1] = &testbench.ICMPv4{ 147 Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable), 148 Code: testbench.ICMPv4Code(tt.code), 149 Payload: bytes, 150 } 151 conn.SendFrameStateless(t, layers) 152 } 153 154 dut.Send(t, acceptedFd, []byte("Hello"), 0) 155 156 time.Sleep(500 * time.Millisecond) 157 if _, err := dut.SendWithErrno(context.Background(), t, acceptedFd, []byte("Hello"), 0); err != tt.wantErrno { 158 t.Fatalf("got send() = %s(%d), want %s(%d)", err, err, tt.wantErrno, tt.wantErrno) 159 } 160 }) 161 } 162 } 163 164 // TestTCPSynSentUnreachable6 verifies that TCP connections fail immediately when 165 // an ICMP destination unreachable message is sent in response to the inital 166 // SYN. 167 func TestTCPSynSentUnreachable6(t *testing.T) { 168 // Create the DUT and connection. 169 dut := testbench.NewDUT(t) 170 clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv6) 171 conn := dut.Net.NewTCPIPv6(t, testbench.TCP{DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort}) 172 defer conn.Close(t) 173 174 // Bring the DUT to SYN-SENT state with a non-blocking connect. 175 sa := unix.SockaddrInet6{ 176 Port: int(conn.SrcPort()), 177 ZoneId: dut.Net.RemoteDevID, 178 } 179 copy(sa.Addr[:], dut.Net.LocalIPv6) 180 if _, err := dut.ConnectWithErrno(context.Background(), t, clientFD, &sa); err != unix.EINPROGRESS { 181 t.Errorf("got connect() = %v, want EINPROGRESS", err) 182 } 183 184 // Get the SYN. 185 tcp, err := conn.Expect(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second) 186 if err != nil { 187 t.Fatalf("expected SYN: %s", err) 188 } 189 190 // Send a host unreachable message. 191 icmpPayload := testbench.Layers{tcp.Prev(), tcp} 192 bytes, err := icmpPayload.ToBytes() 193 if err != nil { 194 t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err) 195 } 196 197 layers := conn.CreateFrame(t, nil) 198 layers[len(layers)-1] = &testbench.ICMPv6{ 199 Type: testbench.ICMPv6Type(header.ICMPv6DstUnreachable), 200 Code: testbench.ICMPv6Code(header.ICMPv6NetworkUnreachable), 201 Payload: bytes, 202 } 203 conn.SendFrameStateless(t, layers) 204 205 if err := getConnectError(t, &dut, clientFD); err != unix.ENETUNREACH { 206 t.Errorf("got connect() = %v, want EHOSTUNREACH", err) 207 } 208 } 209 210 // getConnectError gets the errno generated by the on-going connect attempt on 211 // fd. fd must be non-blocking and there must be a connect call to fd which 212 // returned EINPROGRESS before. These conditions are guaranteed in this test. 213 func getConnectError(t *testing.T, dut *testbench.DUT, fd int32) error { 214 t.Helper() 215 // We previously got EINPROGRESS form the connect call. We can 216 // handle it as explained by connect(2): 217 // EINPROGRESS: 218 // The socket is nonblocking and the connection cannot be 219 // completed immediately. It is possible to select(2) or poll(2) 220 // for completion by selecting the socket for writing. After 221 // select(2) indicates writability, use getsockopt(2) to read 222 // the SO_ERROR option at level SOL_SOCKET to determine 223 // whether connect() completed successfully (SO_ERROR is 224 // zero) or unsuccessfully (SO_ERROR is one of the usual 225 // error codes listed here, explaining the reason for the 226 // failure). 227 dut.PollOne(t, fd, unix.POLLOUT, 10*time.Second) 228 if errno := dut.GetSockOptInt(t, fd, unix.SOL_SOCKET, unix.SO_ERROR); errno != 0 { 229 return unix.Errno(errno) 230 } 231 return nil 232 }