github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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 "github.com/SagerNet/gvisor/pkg/tcpip/header" 25 "github.com/SagerNet/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 // Create the DUT and connection. 37 dut := testbench.NewDUT(t) 38 clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv4) 39 port := uint16(9001) 40 conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port}) 41 defer conn.Close(t) 42 43 // Bring the DUT to SYN-SENT state with a non-blocking connect. 44 sa := unix.SockaddrInet4{Port: int(port)} 45 copy(sa.Addr[:], dut.Net.LocalIPv4) 46 if _, err := dut.ConnectWithErrno(context.Background(), t, clientFD, &sa); err != unix.EINPROGRESS { 47 t.Errorf("got connect() = %v, want EINPROGRESS", err) 48 } 49 50 // Get the SYN. 51 tcp, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second) 52 if err != nil { 53 t.Fatalf("expected SYN: %s", err) 54 } 55 56 // Send a host unreachable message. 57 icmpPayload := testbench.Layers{tcp.Prev(), tcp} 58 bytes, err := icmpPayload.ToBytes() 59 if err != nil { 60 t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err) 61 } 62 63 layers := conn.CreateFrame(t, nil) 64 layers[len(layers)-1] = &testbench.ICMPv4{ 65 Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable), 66 Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable), 67 Payload: bytes, 68 } 69 conn.SendFrameStateless(t, layers) 70 71 if err := getConnectError(t, &dut, clientFD); err != unix.EHOSTUNREACH { 72 t.Errorf("got connect() = %v, want EHOSTUNREACH", err) 73 } 74 } 75 76 // TestTCPSynSentUnreachable6 verifies that TCP connections fail immediately when 77 // an ICMP destination unreachable message is sent in response to the inital 78 // SYN. 79 func TestTCPSynSentUnreachable6(t *testing.T) { 80 // Create the DUT and connection. 81 dut := testbench.NewDUT(t) 82 clientFD, clientPort := dut.CreateBoundSocket(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, dut.Net.RemoteIPv6) 83 conn := dut.Net.NewTCPIPv6(t, testbench.TCP{DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort}) 84 defer conn.Close(t) 85 86 // Bring the DUT to SYN-SENT state with a non-blocking connect. 87 sa := unix.SockaddrInet6{ 88 Port: int(conn.SrcPort()), 89 ZoneId: dut.Net.RemoteDevID, 90 } 91 copy(sa.Addr[:], dut.Net.LocalIPv6) 92 if _, err := dut.ConnectWithErrno(context.Background(), t, clientFD, &sa); err != unix.EINPROGRESS { 93 t.Errorf("got connect() = %v, want EINPROGRESS", err) 94 } 95 96 // Get the SYN. 97 tcp, err := conn.Expect(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second) 98 if err != nil { 99 t.Fatalf("expected SYN: %s", err) 100 } 101 102 // Send a host unreachable message. 103 icmpPayload := testbench.Layers{tcp.Prev(), tcp} 104 bytes, err := icmpPayload.ToBytes() 105 if err != nil { 106 t.Fatalf("got icmpPayload.ToBytes() = (_, %s), want = (_, nil)", err) 107 } 108 109 layers := conn.CreateFrame(t, nil) 110 layers[len(layers)-1] = &testbench.ICMPv6{ 111 Type: testbench.ICMPv6Type(header.ICMPv6DstUnreachable), 112 Code: testbench.ICMPv6Code(header.ICMPv6NetworkUnreachable), 113 Payload: bytes, 114 } 115 conn.SendFrameStateless(t, layers) 116 117 if err := getConnectError(t, &dut, clientFD); err != unix.ENETUNREACH { 118 t.Errorf("got connect() = %v, want EHOSTUNREACH", err) 119 } 120 } 121 122 // getConnectError gets the errno generated by the on-going connect attempt on 123 // fd. fd must be non-blocking and there must be a connect call to fd which 124 // returned EINPROGRESS before. These conditions are guaranteed in this test. 125 func getConnectError(t *testing.T, dut *testbench.DUT, fd int32) error { 126 t.Helper() 127 // We previously got EINPROGRESS form the connect call. We can 128 // handle it as explained by connect(2): 129 // EINPROGRESS: 130 // The socket is nonblocking and the connection cannot be 131 // completed immediately. It is possible to select(2) or poll(2) 132 // for completion by selecting the socket for writing. After 133 // select(2) indicates writability, use getsockopt(2) to read 134 // the SO_ERROR option at level SOL_SOCKET to determine 135 // whether connect() completed successfully (SO_ERROR is 136 // zero) or unsuccessfully (SO_ERROR is one of the usual 137 // error codes listed here, explaining the reason for the 138 // failure). 139 dut.PollOne(t, fd, unix.POLLOUT, 10*time.Second) 140 if errno := dut.GetSockOptInt(t, fd, unix.SOL_SOCKET, unix.SO_ERROR); errno != 0 { 141 return unix.Errno(errno) 142 } 143 return nil 144 }