diff options
Diffstat (limited to 'test')
18 files changed, 756 insertions, 302 deletions
diff --git a/test/packetimpact/runner/dut.go b/test/packetimpact/runner/dut.go index 02678a76a..f27e52f93 100644 --- a/test/packetimpact/runner/dut.go +++ b/test/packetimpact/runner/dut.go @@ -331,6 +331,22 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co t.Logf("sniffer logs:\n%s", snifferOut) } }) + } + + // Arm the cleanup hook before we do anthing else. Otherwise failures below + // can cause the test to hang in the cleanup hook above. + t.Cleanup(func() { + // Wait 1 second before killing tcpdump to give it time to flush + // any packets. On linux tests killing it immediately can + // sometimes result in partial pcaps. + time.Sleep(1 * time.Second) + if logs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, "killall", baseSnifferArgs[0]); err != nil { + t.Errorf("failed to kill all sniffers: %s, logs: %s", err, logs) + } + }) + + for _, info := range dutInfos { + n := info.Net // When the Linux kernel receives a SYN-ACK for a SYN it didn't send, it // will respond with an RST. In most packetimpact tests, the SYN is sent // by the raw socket, the kernel knows nothing about the connection, this @@ -344,16 +360,6 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co } } - t.Cleanup(func() { - // Wait 1 second before killing tcpdump to give it time to flush - // any packets. On linux tests killing it immediately can - // sometimes result in partial pcaps. - time.Sleep(1 * time.Second) - if logs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, "killall", baseSnifferArgs[0]); err != nil { - t.Errorf("failed to kill all sniffers: %s, logs: %s", err, logs) - } - }) - // FIXME(b/156449515): Some piece of the system has a race. The old // bash script version had a sleep, so we have one too. The race should // be fixed and this sleep removed. diff --git a/test/packetimpact/tests/tcp_listen_backlog_test.go b/test/packetimpact/tests/tcp_listen_backlog_test.go index fea7d5b6f..e124002f6 100644 --- a/test/packetimpact/tests/tcp_listen_backlog_test.go +++ b/test/packetimpact/tests/tcp_listen_backlog_test.go @@ -15,7 +15,9 @@ package tcp_listen_backlog_test import ( + "bytes" "flag" + "sync" "testing" "time" @@ -35,60 +37,272 @@ func init() { func TestTCPListenBacklog(t *testing.T) { dut := testbench.NewDUT(t) - // Listening endpoint accepts one more connection than the listen backlog. - listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 0 /*backlog*/) + // This is the number of pending connections before SYN cookies are used. + const backlog = 10 - var establishedConn testbench.TCPIPv4 - var incompleteConn testbench.TCPIPv4 + listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, backlog) + defer dut.Close(t, listenFd) - // Test if the DUT listener replies to more SYNs than listen backlog+1 - for i, conn := range []*testbench.TCPIPv4{&establishedConn, &incompleteConn} { - *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - // Expect dut connection to have transitioned to SYN-RCVD state. + // Fill the SYN queue with connections in SYN-RCVD. We will use these to test + // that ACKs received while the accept queue is full are ignored. + var synQueueConns [backlog]testbench.TCPIPv4 + defer func() { + for i := range synQueueConns { + synQueueConns[i].Close(t) + } + }() + { + var wg sync.WaitGroup + for i := range synQueueConns { + conn := &synQueueConns[i] + *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) + + wg.Add(1) + go func(i int) { + defer wg.Done() + + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) + if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { + t.Errorf("%d: expected TCP frame: %s", i, err) + } else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want { + t.Errorf("%d: got %s, want %s", i, got, want) + } + }(i) + } + wg.Wait() + if t.Failed() { + t.FailNow() + } + } + + const payloadLen = 1 + payload := testbench.Payload{Bytes: testbench.GenerateRandomPayload(t, payloadLen)} + + // Fill the accept queue with connections established using SYN cookies. + var synCookieConns [backlog + 1]testbench.TCPIPv4 + defer func() { + for i := range synCookieConns { + synCookieConns[i].Close(t) + } + }() + { + var wg sync.WaitGroup + for i := range synCookieConns { + conn := &synCookieConns[i] + *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) + + wg.Add(1) + go func(i int) { + defer wg.Done() + + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) + if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { + t.Errorf("%d: expected TCP frame: %s", i, err) + } else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want { + t.Errorf("%d: got %s, want %s", i, got, want) + } + // Send a payload so we can observe the dut ACK. + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, &payload) + if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { + t.Errorf("%d: expected TCP frame: %s", i, err) + } else if got, want := *got.Flags, header.TCPFlagAck; got != want { + t.Errorf("%d: got %s, want %s", i, got, want) + } + }(i) + } + wg.Wait() + if t.Failed() { + t.FailNow() + } + } + + // Send ACKs to complete the handshakes. These are expected to be dropped + // because the accept queue is full. + { + var wg sync.WaitGroup + for i := range synQueueConns { + conn := &synQueueConns[i] + wg.Add(1) + go func(i int) { + defer wg.Done() + + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) + // Wait for the SYN-ACK to be retransmitted to confirm the ACK was + // dropped. + seqNum := uint32(*conn.RemoteSeqNum(t) - 1) + if got, err := conn.Expect(t, testbench.TCP{SeqNum: &seqNum}, time.Second); err != nil { + t.Errorf("%d: expected TCP frame: %s", i, err) + } else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want { + t.Errorf("%d: got %s, want %s", i, got, want) + } + }(i) + } + + wg.Wait() + if t.Failed() { + t.FailNow() + } + } + + // While the accept queue is still full, send an unexpected ACK from a new + // socket. The listener should reply with an RST. + func() { + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) + defer conn.Close(t) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) + if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { + t.Errorf("expected TCP frame: %s", err) + } else if got, want := *got.Flags, header.TCPFlagRst; got != want { + t.Errorf("got %s, want %s", got, want) + } + }() + + func() { + // Now initiate a new connection when the accept queue is full. + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) + defer conn.Close(t) + // Expect dut connection to drop the SYN. conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected SYN-ACK for %d connection, %s", i, err) + if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err == nil { + t.Fatalf("expected no TCP frame, got %s", got) + } + }() + + // Drain the accept queue. + { + var wg sync.WaitGroup + for i := range synCookieConns { + conn := &synCookieConns[i] + + wg.Add(1) + go func(i int) { + defer wg.Done() + + fd, _ := dut.Accept(t, listenFd) + b := dut.Recv(t, fd, payloadLen+1, 0) + dut.Close(t, fd) + if !bytes.Equal(b, payload.Bytes) { + t.Errorf("connection %d: got dut.Recv = %x, want = %x", i, b, payload.Bytes) + } + + if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { + t.Errorf("%d: expected TCP frame: %s", i, err) + } else if got, want := *got.Flags, header.TCPFlagFin|header.TCPFlagAck; got != want { + t.Errorf("%d: got %s, want %s", i, got, want) + } + + // Prevent retransmission. + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) + }(i) + } + wg.Wait() + if t.Failed() { + t.FailNow() } } - defer establishedConn.Close(t) - defer incompleteConn.Close(t) - - // Send the ACK to complete handshake. - establishedConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - - // Poll for the established connection ready for accept. - dut.PollOne(t, listenFd, unix.POLLIN, time.Second) - - // Send the ACK to complete handshake, expect this to be dropped by the - // listener as the accept queue would be full because of the previous - // handshake. - incompleteConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - // Let the test wait for sometime so that the ACK is indeed dropped by - // the listener. Without such a wait, the DUT accept can race with - // ACK handling (dropping) causing the test to be flaky. - time.Sleep(100 * time.Millisecond) - - // Drain the accept queue to enable poll for subsequent connections on the - // listener. - fd, _ := dut.Accept(t, listenFd) - dut.Close(t, fd) - - // The ACK for the incomplete connection should be ignored by the - // listening endpoint and the poll on listener should now time out. - if pfds := dut.Poll(t, []unix.PollFd{{Fd: listenFd, Events: unix.POLLIN}}, time.Second); len(pfds) != 0 { - t.Fatalf("got dut.Poll(...) = %#v", pfds) + + // Complete the partial connections to move them from the SYN queue to the + // accept queue. We will use these to test that connections in the accept + // queue are closed on listener shutdown. + { + var wg sync.WaitGroup + for i := range synQueueConns { + conn := &synQueueConns[i] + wg.Add(1) + go func(i int) { + defer wg.Done() + + tcp := testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)} + + // Exercise connections with and without pending data. + if i%2 == 0 { + // Send ACK with no payload; wait for absence of SYN-ACK retransmit. + conn.Send(t, tcp) + if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err == nil { + t.Errorf("%d: expected no TCP frame, got %s", i, got) + } + } else { + // Send ACK with payload; wait for ACK. + conn.Send(t, tcp, &payload) + if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { + t.Errorf("%d: expected TCP frame: %s", i, err) + } else if got, want := *got.Flags, header.TCPFlagAck; got != want { + t.Errorf("%d: got %s, want %s", i, got, want) + } + } + }(i) + } + + wg.Wait() + if t.Failed() { + t.FailNow() + } } - // Re-send the ACK to complete handshake and re-fill the accept-queue. - incompleteConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) - dut.PollOne(t, listenFd, unix.POLLIN, time.Second) - - // Now initiate a new connection when the accept queue is full. - connectingConn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) - defer connectingConn.Close(t) - // Expect dut connection to drop the SYN and let the client stay in SYN_SENT state. - connectingConn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) - if got, err := connectingConn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err == nil { - t.Fatalf("expected no SYN-ACK, but got %s", got) + // The accept queue now has N-1 connections in it. The next incoming SYN will + // enter the SYN queue, and the one following will use SYN cookies. We test + // both. + var connectingConns [2]testbench.TCPIPv4 + defer func() { + for i := range connectingConns { + connectingConns[i].Close(t) + } + }() + { + var wg sync.WaitGroup + for i := range connectingConns { + conn := &connectingConns[i] + *conn = dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{}) + + wg.Add(1) + go func(i int) { + defer wg.Done() + + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) + if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { + t.Errorf("%d: expected TCP frame: %s", i, err) + } else if got, want := *got.Flags, header.TCPFlagSyn|header.TCPFlagAck; got != want { + t.Errorf("%d: got %s, want %s", i, got, want) + } + }(i) + } + wg.Wait() + if t.Failed() { + t.FailNow() + } + } + + dut.Shutdown(t, listenFd, unix.SHUT_RD) + + var wg sync.WaitGroup + + // Shutdown causes Connections in the accept queue to be closed. + for i := range synQueueConns { + conn := &synQueueConns[i] + wg.Add(1) + go func(i int) { + defer wg.Done() + + if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err != nil { + t.Errorf("%d: expected TCP frame: %s", i, err) + } else if got, want := *got.Flags, header.TCPFlagRst|header.TCPFlagAck; got != want { + t.Errorf("%d: got %s, want %s", i, got, want) + } + }(i) + } + + for i := range connectingConns { + conn := &connectingConns[i] + + wg.Add(1) + go func(i int) { + defer wg.Done() + + if got, err := conn.Expect(t, testbench.TCP{}, time.Second); err == nil { + t.Errorf("%d: expected no TCP frame, got %s", i, got) + } + }(i) } + + wg.Wait() } diff --git a/test/runtimes/runner/lib/BUILD b/test/runtimes/runner/lib/BUILD index d308f41b0..3491c535b 100644 --- a/test/runtimes/runner/lib/BUILD +++ b/test/runtimes/runner/lib/BUILD @@ -5,7 +5,11 @@ package(licenses = ["notice"]) go_library( name = "lib", testonly = 1, - srcs = ["lib.go"], + srcs = [ + "go_test_dependency_go118.go", + "go_test_dependency_not_go118.go", + "lib.go", + ], visibility = ["//test/runtimes/runner:__pkg__"], deps = [ "//pkg/log", diff --git a/test/runtimes/runner/lib/go_test_dependency_go118.go b/test/runtimes/runner/lib/go_test_dependency_go118.go new file mode 100644 index 000000000..d430e81c7 --- /dev/null +++ b/test/runtimes/runner/lib/go_test_dependency_go118.go @@ -0,0 +1,27 @@ +// Copyright 2021 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build go1.18 && go1.1 +// +build go1.18,go1.1 + +package lib + +import ( + "testing" +) + +// mainStart wraps testing.MainStart for Go release == 1.18. +func mainStart(tests []testing.InternalTest) *testing.M { + return testing.MainStart(testDeps{}, tests, nil, nil, nil) +} diff --git a/test/runtimes/runner/lib/go_test_dependency_not_go118.go b/test/runtimes/runner/lib/go_test_dependency_not_go118.go new file mode 100644 index 000000000..8b0b34c72 --- /dev/null +++ b/test/runtimes/runner/lib/go_test_dependency_not_go118.go @@ -0,0 +1,25 @@ +// Copyright 2021 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !go1.18 && go1.1 +// +build !go1.18,go1.1 + +package lib + +import "testing" + +// mainStart wraps testing.MainStart for Go release < 1.18. +func mainStart(tests []testing.InternalTest) *testing.M { + return testing.MainStart(testDeps{}, tests, nil, nil) +} diff --git a/test/runtimes/runner/lib/lib.go b/test/runtimes/runner/lib/lib.go index d6b652897..d704f8895 100644 --- a/test/runtimes/runner/lib/lib.go +++ b/test/runtimes/runner/lib/lib.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "os" + "reflect" "sort" "strings" "testing" @@ -63,8 +64,7 @@ func RunTests(lang, image, excludeFile string, batchSize int, timeout time.Durat fmt.Fprintf(os.Stderr, "%s\n", err.Error()) return 1 } - - m := testing.MainStart(testDeps{}, tests, nil, nil) + m := mainStart(tests) return m.Run() } @@ -197,3 +197,21 @@ func (f testDeps) ImportPath() string { return "" } func (f testDeps) StartTestLog(io.Writer) {} func (f testDeps) StopTestLog() error { return nil } func (f testDeps) SetPanicOnExit0(bool) {} +func (f testDeps) CoordinateFuzzing(time.Duration, int64, time.Duration, int64, int, []corpusEntry, []reflect.Type, string, string) error { + return nil +} +func (f testDeps) RunFuzzWorker(func(corpusEntry) error) error { return nil } +func (f testDeps) ReadCorpus(string, []reflect.Type) ([]corpusEntry, error) { return nil, nil } +func (f testDeps) CheckCorpus([]interface{}, []reflect.Type) error { return nil } +func (f testDeps) ResetCoverage() {} +func (f testDeps) SnapshotCoverage() {} + +// Copied from testing/fuzz.go. +type corpusEntry = struct { + Parent string + Name string + Data []byte + Values []interface{} + Generation int + IsSeed bool +} diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 5b882875f..b96005d7d 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -590,6 +590,7 @@ cc_binary( "//test/util:posix_error", "//test/util:test_main", "//test/util:test_util", + "//test/util:thread_util", ], ) @@ -1978,6 +1979,7 @@ cc_binary( defines = select_system(), linkstatic = 1, deps = [ + ":ip_socket_test_util", ":unix_domain_socket_test_util", "//test/util:capability_util", "//test/util:file_descriptor", @@ -2660,16 +2662,11 @@ cc_library( cc_library( name = "socket_ip_udp_unbound_external_networking", testonly = 1, - srcs = [ - "socket_ip_udp_unbound_external_networking.cc", - ], hdrs = [ "socket_ip_udp_unbound_external_networking.h", ], deps = [ ":ip_socket_test_util", - "//test/util:socket_util", - "//test/util:test_util", ], alwayslink = 1, ) @@ -2685,6 +2682,9 @@ cc_library( ], deps = [ ":socket_ip_udp_unbound_external_networking", + "//test/util:socket_util", + "//test/util:test_util", + "@com_google_absl//absl/cleanup", gtest, ], alwayslink = 1, diff --git a/test/syscalls/linux/epoll.cc b/test/syscalls/linux/epoll.cc index 3ef8b0327..c2dc8174c 100644 --- a/test/syscalls/linux/epoll.cc +++ b/test/syscalls/linux/epoll.cc @@ -30,6 +30,7 @@ #include "test/util/file_descriptor.h" #include "test/util/posix_error.h" #include "test/util/test_util.h" +#include "test/util/thread_util.h" namespace gvisor { namespace testing { @@ -496,6 +497,41 @@ TEST(EpollTest, PipeReaderHupAfterWriterClosed) { EXPECT_EQ(result[0].data.u64, kMagicConstant); } +TEST(EpollTest, DoubleLayerEpoll) { + int pipefds[2]; + ASSERT_THAT(pipe2(pipefds, O_NONBLOCK), SyscallSucceeds()); + FileDescriptor rfd(pipefds[0]); + FileDescriptor wfd(pipefds[1]); + + auto epfd1 = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + ASSERT_NO_ERRNO( + RegisterEpollFD(epfd1.get(), rfd.get(), EPOLLIN | EPOLLHUP, rfd.get())); + + auto epfd2 = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + ASSERT_NO_ERRNO(RegisterEpollFD(epfd2.get(), epfd1.get(), EPOLLIN | EPOLLHUP, + epfd1.get())); + + // Write to wfd and then check if epoll events were generated correctly. + // Run this loop a couple of times to check if event in epfd1 is cleaned. + constexpr char data[] = "data"; + for (int i = 0; i < 2; ++i) { + ScopedThread thread1([&wfd, &data]() { + sleep(1); + ASSERT_EQ(WriteFd(wfd.get(), data, sizeof(data)), sizeof(data)); + }); + + struct epoll_event ret_events[2]; + ASSERT_THAT(RetryEINTR(epoll_wait)(epfd2.get(), ret_events, 2, 5000), + SyscallSucceedsWithValue(1)); + ASSERT_EQ(ret_events[0].data.fd, epfd1.get()); + ASSERT_THAT(RetryEINTR(epoll_wait)(epfd1.get(), ret_events, 2, 5000), + SyscallSucceedsWithValue(1)); + ASSERT_EQ(ret_events[0].data.fd, rfd.get()); + char readBuf[sizeof(data)]; + ASSERT_EQ(ReadFd(rfd.get(), readBuf, sizeof(data)), sizeof(data)); + } +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/ip_socket_test_util.cc b/test/syscalls/linux/ip_socket_test_util.cc index a1216d23f..d18e616d0 100644 --- a/test/syscalls/linux/ip_socket_test_util.cc +++ b/test/syscalls/linux/ip_socket_test_util.cc @@ -16,6 +16,7 @@ #include <net/if.h> #include <netinet/in.h> +#include <netpacket/packet.h> #include <sys/socket.h> #include <cstring> @@ -196,75 +197,53 @@ SocketKind IPv6TCPUnboundSocket(int type) { UnboundSocketCreator(AF_INET6, type | SOCK_STREAM, IPPROTO_TCP)}; } -PosixError IfAddrHelper::Load() { - Release(); -#ifndef ANDROID - RETURN_ERROR_IF_SYSCALL_FAIL(getifaddrs(&ifaddr_)); -#else - // Android does not support getifaddrs in r22. - return PosixError(ENOSYS, "getifaddrs"); -#endif - return NoError(); -} - -void IfAddrHelper::Release() { - if (ifaddr_) { -#ifndef ANDROID - // Android does not support freeifaddrs in r22. - freeifaddrs(ifaddr_); -#endif - ifaddr_ = nullptr; - } -} - -std::vector<std::string> IfAddrHelper::InterfaceList(int family) const { - std::vector<std::string> names; - for (auto ifa = ifaddr_; ifa != NULL; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != family) { - continue; - } - names.emplace(names.end(), ifa->ifa_name); - } - return names; -} - -const sockaddr* IfAddrHelper::GetAddr(int family, std::string name) const { - for (auto ifa = ifaddr_; ifa != NULL; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != family) { - continue; - } - if (name == ifa->ifa_name) { - return ifa->ifa_addr; - } - } - return nullptr; -} - -PosixErrorOr<int> IfAddrHelper::GetIndex(std::string name) const { - return InterfaceIndex(name); -} - std::string GetAddr4Str(const in_addr* a) { char str[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, a, str, sizeof(str)); - return std::string(str); + return inet_ntop(AF_INET, a, str, sizeof(str)); } std::string GetAddr6Str(const in6_addr* a) { char str[INET6_ADDRSTRLEN]; - inet_ntop(AF_INET6, a, str, sizeof(str)); - return std::string(str); + return inet_ntop(AF_INET6, a, str, sizeof(str)); } std::string GetAddrStr(const sockaddr* a) { - if (a->sa_family == AF_INET) { - auto src = &(reinterpret_cast<const sockaddr_in*>(a)->sin_addr); - return GetAddr4Str(src); - } else if (a->sa_family == AF_INET6) { - auto src = &(reinterpret_cast<const sockaddr_in6*>(a)->sin6_addr); - return GetAddr6Str(src); + switch (a->sa_family) { + case AF_INET: { + return GetAddr4Str(&(reinterpret_cast<const sockaddr_in*>(a)->sin_addr)); + } + case AF_INET6: { + return GetAddr6Str( + &(reinterpret_cast<const sockaddr_in6*>(a)->sin6_addr)); + } + case AF_PACKET: { + const sockaddr_ll& ll = *reinterpret_cast<const sockaddr_ll*>(a); + std::ostringstream ss; + ss << std::hex; + ss << std::showbase; + ss << '{'; + ss << " protocol=" << ntohs(ll.sll_protocol); + ss << " ifindex=" << ll.sll_ifindex; + ss << " hatype=" << ll.sll_hatype; + ss << " pkttype=" << static_cast<unsigned short>(ll.sll_pkttype); + if (ll.sll_halen != 0) { + ss << " addr="; + for (unsigned char i = 0; i < ll.sll_halen; ++i) { + if (i != 0) { + ss << ':'; + } + ss << static_cast<unsigned short>(ll.sll_addr[i]); + } + } + ss << " }"; + return ss.str(); + } + default: { + std::ostringstream ss; + ss << "invalid(sa_family=" << a->sa_family << ")"; + return ss.str(); + } } - return std::string("<invalid>"); } } // namespace testing diff --git a/test/syscalls/linux/ip_socket_test_util.h b/test/syscalls/linux/ip_socket_test_util.h index 556838356..957006e25 100644 --- a/test/syscalls/linux/ip_socket_test_util.h +++ b/test/syscalls/linux/ip_socket_test_util.h @@ -115,25 +115,6 @@ SocketKind IPv4TCPUnboundSocket(int type); // created with AF_INET6, SOCK_STREAM, IPPROTO_TCP and the given type. SocketKind IPv6TCPUnboundSocket(int type); -// IfAddrHelper is a helper class that determines the local interfaces present -// and provides functions to obtain their names, index numbers, and IP address. -class IfAddrHelper { - public: - IfAddrHelper() : ifaddr_(nullptr) {} - ~IfAddrHelper() { Release(); } - - PosixError Load(); - void Release(); - - std::vector<std::string> InterfaceList(int family) const; - - const sockaddr* GetAddr(int family, std::string name) const; - PosixErrorOr<int> GetIndex(std::string name) const; - - private: - struct ifaddrs* ifaddr_; -}; - // GetAddr4Str returns the given IPv4 network address structure as a string. std::string GetAddr4Str(const in_addr* a); diff --git a/test/syscalls/linux/proc_isolated.cc b/test/syscalls/linux/proc_isolated.cc index a38689667..38d079d2b 100644 --- a/test/syscalls/linux/proc_isolated.cc +++ b/test/syscalls/linux/proc_isolated.cc @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <linux/msg.h> #include <linux/sem.h> #include <linux/shm.h> @@ -73,6 +74,27 @@ TEST(ProcDefaults, PresenceOfSem) { ASSERT_EQ(semmni, SEMMNI); } +TEST(ProcDefaults, PresenceOfMsgMniMaxMnb) { + uint64_t msgmni = 0; + uint64_t msgmax = 0; + uint64_t msgmnb = 0; + + std::string proc_file; + proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/msgmni")); + ASSERT_FALSE(proc_file.empty()); + ASSERT_TRUE(absl::SimpleAtoi(proc_file, &msgmni)); + proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/msgmax")); + ASSERT_FALSE(proc_file.empty()); + ASSERT_TRUE(absl::SimpleAtoi(proc_file, &msgmax)); + proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/msgmnb")); + ASSERT_FALSE(proc_file.empty()); + ASSERT_TRUE(absl::SimpleAtoi(proc_file, &msgmnb)); + + ASSERT_EQ(msgmni, MSGMNI); + ASSERT_EQ(msgmax, MSGMAX); + ASSERT_EQ(msgmnb, MSGMNB); +} + } // namespace } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/raw_socket.cc b/test/syscalls/linux/raw_socket.cc index ef1db47ee..ef176cbee 100644 --- a/test/syscalls/linux/raw_socket.cc +++ b/test/syscalls/linux/raw_socket.cc @@ -25,6 +25,7 @@ #include <algorithm> #include "gtest/gtest.h" +#include "test/syscalls/linux/ip_socket_test_util.h" #include "test/syscalls/linux/unix_domain_socket_test_util.h" #include "test/util/capability_util.h" #include "test/util/file_descriptor.h" @@ -39,6 +40,9 @@ namespace testing { namespace { +using ::testing::IsNull; +using ::testing::NotNull; + // Fixture for tests parameterized by protocol. class RawSocketTest : public ::testing::TestWithParam<std::tuple<int, int>> { protected: @@ -1057,6 +1061,131 @@ TEST(RawSocketTest, BindReceive) { ASSERT_NO_FATAL_FAILURE(TestRawSocketMaybeBindReceive(true /* do_bind */)); } +TEST(RawSocketTest, ReceiveIPPacketInfo) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); + + FileDescriptor raw = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_UDP)); + + const sockaddr_in addr_ = { + .sin_family = AF_INET, + .sin_addr = {.s_addr = htonl(INADDR_LOOPBACK)}, + }; + ASSERT_THAT( + bind(raw.get(), reinterpret_cast<const sockaddr*>(&addr_), sizeof(addr_)), + SyscallSucceeds()); + + // Register to receive IP packet info. + constexpr int one = 1; + ASSERT_THAT(setsockopt(raw.get(), IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)), + SyscallSucceeds()); + + constexpr char send_buf[] = "malformed UDP"; + ASSERT_THAT(sendto(raw.get(), send_buf, sizeof(send_buf), 0 /* flags */, + reinterpret_cast<const sockaddr*>(&addr_), sizeof(addr_)), + SyscallSucceedsWithValue(sizeof(send_buf))); + + struct { + iphdr ip; + char data[sizeof(send_buf)]; + + // Extra space in the receive buffer should be unused. + char unused_space; + } ABSL_ATTRIBUTE_PACKED recv_buf; + iovec recv_iov = { + .iov_base = &recv_buf, + .iov_len = sizeof(recv_buf), + }; + in_pktinfo received_pktinfo; + char recv_cmsg_buf[CMSG_SPACE(sizeof(received_pktinfo))]; + msghdr recv_msg = { + .msg_iov = &recv_iov, + .msg_iovlen = 1, + .msg_control = recv_cmsg_buf, + .msg_controllen = CMSG_LEN(sizeof(received_pktinfo)), + }; + ASSERT_THAT(RetryEINTR(recvmsg)(raw.get(), &recv_msg, 0), + SyscallSucceedsWithValue(sizeof(iphdr) + sizeof(send_buf))); + EXPECT_EQ(memcmp(send_buf, &recv_buf.data, sizeof(send_buf)), 0); + EXPECT_EQ(recv_buf.ip.version, static_cast<unsigned int>(IPVERSION)); + // IHL holds the number of header bytes in 4 byte units. + EXPECT_EQ(recv_buf.ip.ihl, sizeof(iphdr) / 4); + EXPECT_EQ(ntohs(recv_buf.ip.tot_len), sizeof(iphdr) + sizeof(send_buf)); + EXPECT_EQ(recv_buf.ip.protocol, IPPROTO_UDP); + EXPECT_EQ(ntohl(recv_buf.ip.saddr), INADDR_LOOPBACK); + EXPECT_EQ(ntohl(recv_buf.ip.daddr), INADDR_LOOPBACK); + + cmsghdr* cmsg = CMSG_FIRSTHDR(&recv_msg); + ASSERT_THAT(cmsg, NotNull()); + EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(received_pktinfo))); + EXPECT_EQ(cmsg->cmsg_level, IPPROTO_IP); + EXPECT_EQ(cmsg->cmsg_type, IP_PKTINFO); + memcpy(&received_pktinfo, CMSG_DATA(cmsg), sizeof(received_pktinfo)); + EXPECT_EQ(received_pktinfo.ipi_ifindex, + ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())); + EXPECT_EQ(ntohl(received_pktinfo.ipi_spec_dst.s_addr), INADDR_LOOPBACK); + EXPECT_EQ(ntohl(received_pktinfo.ipi_addr.s_addr), INADDR_LOOPBACK); + + EXPECT_THAT(CMSG_NXTHDR(&recv_msg, cmsg), IsNull()); +} + +TEST(RawSocketTest, ReceiveIPv6PacketInfo) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); + + FileDescriptor raw = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_RAW, IPPROTO_UDP)); + + const sockaddr_in6 addr_ = { + .sin6_family = AF_INET6, + .sin6_addr = in6addr_loopback, + }; + ASSERT_THAT( + bind(raw.get(), reinterpret_cast<const sockaddr*>(&addr_), sizeof(addr_)), + SyscallSucceeds()); + + // Register to receive IPv6 packet info. + constexpr int one = 1; + ASSERT_THAT( + setsockopt(raw.get(), IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)), + SyscallSucceeds()); + + constexpr char send_buf[] = "malformed UDP"; + ASSERT_THAT(sendto(raw.get(), send_buf, sizeof(send_buf), 0 /* flags */, + reinterpret_cast<const sockaddr*>(&addr_), sizeof(addr_)), + SyscallSucceedsWithValue(sizeof(send_buf))); + + char recv_buf[sizeof(send_buf) + 1]; + iovec recv_iov = { + .iov_base = recv_buf, + .iov_len = sizeof(recv_buf), + }; + in6_pktinfo received_pktinfo; + char recv_cmsg_buf[CMSG_SPACE(sizeof(received_pktinfo))]; + msghdr recv_msg = { + .msg_iov = &recv_iov, + .msg_iovlen = 1, + .msg_control = recv_cmsg_buf, + .msg_controllen = CMSG_LEN(sizeof(received_pktinfo)), + }; + ASSERT_THAT(RetryEINTR(recvmsg)(raw.get(), &recv_msg, 0), + SyscallSucceedsWithValue(sizeof(send_buf))); + EXPECT_EQ(memcmp(send_buf, recv_buf, sizeof(send_buf)), 0); + + cmsghdr* cmsg = CMSG_FIRSTHDR(&recv_msg); + ASSERT_THAT(cmsg, NotNull()); + EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(received_pktinfo))); + EXPECT_EQ(cmsg->cmsg_level, IPPROTO_IPV6); + EXPECT_EQ(cmsg->cmsg_type, IPV6_PKTINFO); + memcpy(&received_pktinfo, CMSG_DATA(cmsg), sizeof(received_pktinfo)); + EXPECT_EQ(received_pktinfo.ipi6_ifindex, + ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())); + ASSERT_EQ(memcmp(&received_pktinfo.ipi6_addr, &in6addr_loopback, + sizeof(in6addr_loopback)), + 0); + + EXPECT_THAT(CMSG_NXTHDR(&recv_msg, cmsg), IsNull()); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/socket_ip_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ip_udp_unbound_external_networking.cc deleted file mode 100644 index af2459a2f..000000000 --- a/test/syscalls/linux/socket_ip_udp_unbound_external_networking.cc +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "test/syscalls/linux/socket_ip_udp_unbound_external_networking.h" - -#include "test/util/socket_util.h" -#include "test/util/test_util.h" - -namespace gvisor { -namespace testing { - -void IPUDPUnboundExternalNetworkingSocketTest::SetUp() { - // FIXME(b/137899561): Linux instance for syscall tests sometimes misses its - // IPv4 address on eth0. - found_net_interfaces_ = false; - - // Get interface list. - ASSERT_NO_ERRNO(if_helper_.Load()); - std::vector<std::string> if_names = if_helper_.InterfaceList(AF_INET); - if (if_names.size() != 2) { - return; - } - - // Figure out which interface is where. - std::string lo = if_names[0]; - std::string eth = if_names[1]; - if (lo != "lo") std::swap(lo, eth); - if (lo != "lo") return; - - lo_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(lo)); - auto lo_if_addr = if_helper_.GetAddr(AF_INET, lo); - if (lo_if_addr == nullptr) { - return; - } - lo_if_addr_ = *reinterpret_cast<const sockaddr_in*>(lo_if_addr); - - eth_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(eth)); - auto eth_if_addr = if_helper_.GetAddr(AF_INET, eth); - if (eth_if_addr == nullptr) { - return; - } - eth_if_addr_ = *reinterpret_cast<const sockaddr_in*>(eth_if_addr); - - found_net_interfaces_ = true; -} - -} // namespace testing -} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_udp_unbound_external_networking.h b/test/syscalls/linux/socket_ip_udp_unbound_external_networking.h index 2e8aab129..92c20eba9 100644 --- a/test/syscalls/linux/socket_ip_udp_unbound_external_networking.h +++ b/test/syscalls/linux/socket_ip_udp_unbound_external_networking.h @@ -16,29 +16,13 @@ #define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ #include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/util/socket_util.h" namespace gvisor { namespace testing { // Test fixture for tests that apply to unbound IP UDP sockets in a sandbox // with external networking support. -class IPUDPUnboundExternalNetworkingSocketTest : public SimpleSocketTest { - protected: - void SetUp() override; - - IfAddrHelper if_helper_; - - // found_net_interfaces_ is set to false if SetUp() could not obtain - // all interface infos that we need. - bool found_net_interfaces_; - - // Interface infos. - int lo_if_idx_; - int eth_if_idx_; - sockaddr_in lo_if_addr_; - sockaddr_in eth_if_addr_; -}; +class IPUDPUnboundExternalNetworkingSocketTest : public SimpleSocketTest {}; } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc index c6e775b2a..6c67ec51e 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc @@ -14,9 +14,62 @@ #include "test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h" +#include <net/if.h> + +#include "absl/cleanup/cleanup.h" +#include "test/util/socket_util.h" +#include "test/util/test_util.h" + namespace gvisor { namespace testing { +void IPv4UDPUnboundExternalNetworkingSocketTest::SetUp() { +#ifdef ANDROID + GTEST_SKIP() << "Android does not support getifaddrs in r22"; +#endif + + ifaddrs* ifaddr; + ASSERT_THAT(getifaddrs(&ifaddr), SyscallSucceeds()); + auto cleanup = absl::MakeCleanup([ifaddr] { freeifaddrs(ifaddr); }); + + for (const ifaddrs* ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { + ASSERT_NE(ifa->ifa_name, nullptr); + ASSERT_NE(ifa->ifa_addr, nullptr); + + if (ifa->ifa_addr->sa_family != AF_INET) { + continue; + } + + std::optional<std::pair<int, sockaddr_in>>& if_pair = *[this, ifa]() { + if (strcmp(ifa->ifa_name, "lo") == 0) { + return &lo_if_; + } + return ð_if_; + }(); + + const int if_index = + ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex(ifa->ifa_name)); + + std::cout << " name=" << ifa->ifa_name + << " addr=" << GetAddrStr(ifa->ifa_addr) << " index=" << if_index + << " has_value=" << if_pair.has_value() << std::endl; + + if (if_pair.has_value()) { + continue; + } + + if_pair = std::make_pair( + if_index, *reinterpret_cast<const sockaddr_in*>(ifa->ifa_addr)); + } + + if (!(eth_if_.has_value() && lo_if_.has_value())) { + // FIXME(b/137899561): Linux instance for syscall tests sometimes misses its + // IPv4 address on eth0. + GTEST_SKIP() << " eth_if_.has_value()=" << eth_if_.has_value() + << " lo_if_.has_value()=" << lo_if_.has_value(); + } +} + TestAddress V4EmptyAddress() { TestAddress t("V4Empty"); t.addr.ss_family = AF_INET; @@ -28,7 +81,6 @@ TestAddress V4EmptyAddress() { // the destination port number. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, UDPBroadcastReceivedOnExpectedPort) { - SKIP_IF(!found_net_interfaces_); auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto rcvr1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto rcvr2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); @@ -101,8 +153,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // not a unicast address. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, UDPBroadcastReceivedOnExpectedAddresses) { - SKIP_IF(!found_net_interfaces_); - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto rcvr1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto rcvr2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); @@ -149,7 +199,7 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // Bind the non-receiving socket to the unicast ethernet address. auto norecv_addr = rcv1_addr; reinterpret_cast<sockaddr_in*>(&norecv_addr.addr)->sin_addr = - eth_if_addr_.sin_addr; + eth_if_addr().sin_addr; ASSERT_THAT( bind(norcv->get(), AsSockAddr(&norecv_addr.addr), norecv_addr.addr_len), SyscallSucceedsWithValue(0)); @@ -184,7 +234,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // (UDPBroadcastSendRecvOnSocketBoundToAny). TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, UDPBroadcastSendRecvOnSocketBoundToBroadcast) { - SKIP_IF(!found_net_interfaces_); auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); // Enable SO_BROADCAST. @@ -224,7 +273,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // (UDPBroadcastSendRecvOnSocketBoundToBroadcast). TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, UDPBroadcastSendRecvOnSocketBoundToAny) { - SKIP_IF(!found_net_interfaces_); auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); // Enable SO_BROADCAST. @@ -261,7 +309,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // Verifies that a UDP broadcast fails to send on a socket with SO_BROADCAST // disabled. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendBroadcast) { - SKIP_IF(!found_net_interfaces_); auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); // Broadcast a test message without having enabled SO_BROADCAST on the sending @@ -306,12 +353,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendUnicastOnUnbound) { // set interface or group membership. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastSelfNoGroup) { - // FIXME(b/125485338): A group membership is not required for external - // multicast on gVisor. - SKIP_IF(IsRunningOnGvisor()); - - SKIP_IF(!found_net_interfaces_); - auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto bind_addr = V4Any(); @@ -345,7 +386,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // Check that multicast packets will be delivered to the sending socket without // setting an interface. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastSelf) { - SKIP_IF(!found_net_interfaces_); auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto bind_addr = V4Any(); @@ -388,7 +428,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastSelf) { // set interface and IP_MULTICAST_LOOP disabled. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastSelfLoopOff) { - SKIP_IF(!found_net_interfaces_); auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto bind_addr = V4Any(); @@ -434,12 +473,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // Check that multicast packets won't be delivered to another socket with no // set interface or group membership. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastNoGroup) { - // FIXME(b/125485338): A group membership is not required for external - // multicast on gVisor. - SKIP_IF(IsRunningOnGvisor()); - - SKIP_IF(!found_net_interfaces_); - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); @@ -476,7 +509,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastNoGroup) { // Check that multicast packets will be delivered to another socket without // setting an interface. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticast) { - SKIP_IF(!found_net_interfaces_); auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); @@ -522,7 +554,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticast) { // set interface and IP_MULTICAST_LOOP disabled on the sending socket. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastSenderNoLoop) { - SKIP_IF(!found_net_interfaces_); auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); @@ -572,8 +603,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // setting an interface and IP_MULTICAST_LOOP disabled on the receiving socket. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastReceiverNoLoop) { - SKIP_IF(!found_net_interfaces_); - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); @@ -624,7 +653,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // and both will receive data on it when bound to the ANY address. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastToTwoBoundToAny) { - SKIP_IF(!found_net_interfaces_); auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); std::unique_ptr<FileDescriptor> receivers[2] = { ASSERT_NO_ERRNO_AND_VALUE(NewSocket()), @@ -689,7 +717,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // and both will receive data on it when bound to the multicast address. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastToTwoBoundToMulticastAddress) { - SKIP_IF(!found_net_interfaces_); auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); std::unique_ptr<FileDescriptor> receivers[2] = { ASSERT_NO_ERRNO_AND_VALUE(NewSocket()), @@ -757,7 +784,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // multicast address, both will receive data. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastToTwoBoundToAnyAndMulticastAddress) { - SKIP_IF(!found_net_interfaces_); auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); std::unique_ptr<FileDescriptor> receivers[2] = { ASSERT_NO_ERRNO_AND_VALUE(NewSocket()), @@ -829,8 +855,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // is not a multicast address. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, IpMulticastLoopbackFromAddr) { - SKIP_IF(!found_net_interfaces_); - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); @@ -893,8 +917,6 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // interface, a multicast packet sent out uses the latter as its source address. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, IpMulticastLoopbackIfNicAndAddr) { - SKIP_IF(!found_net_interfaces_); - // Create receiver, bind to ANY and join the multicast group. auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto receiver_addr = V4Any(); @@ -906,11 +928,15 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, &receiver_addr_len), SyscallSucceeds()); EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); - int receiver_port = + const in_port_t receiver_port = reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = lo_if_idx_; + const ip_mreqn group = { + .imr_multiaddr = + { + .s_addr = inet_addr(kMulticastAddress), + }, + .imr_ifindex = lo_if_idx(), + }; ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), SyscallSucceeds()); @@ -918,9 +944,10 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // Set outgoing multicast interface config, with NIC and addr pointing to // different interfaces. auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - ip_mreqn iface = {}; - iface.imr_ifindex = lo_if_idx_; - iface.imr_address = eth_if_addr_.sin_addr; + const ip_mreqn iface = { + .imr_address = eth_if_addr().sin_addr, + .imr_ifindex = lo_if_idx(), + }; ASSERT_THAT(setsockopt(sender->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, sizeof(iface)), SyscallSucceeds()); @@ -928,67 +955,104 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // Send a multicast packet. auto sendto_addr = V4Multicast(); reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port = receiver_port; - char send_buf[4] = {}; + char send_buf[4]; ASSERT_THAT( RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, AsSockAddr(&sendto_addr.addr), sendto_addr.addr_len), SyscallSucceedsWithValue(sizeof(send_buf))); // Receive a multicast packet. - char recv_buf[sizeof(send_buf)] = {}; + char recv_buf[sizeof(send_buf) + 1]; auto src_addr = V4EmptyAddress(); ASSERT_THAT( RetryEINTR(recvfrom)(receiver->get(), recv_buf, sizeof(recv_buf), 0, AsSockAddr(&src_addr.addr), &src_addr.addr_len), - SyscallSucceedsWithValue(sizeof(recv_buf))); - ASSERT_EQ(sizeof(struct sockaddr_in), src_addr.addr_len); - sockaddr_in* src_addr_in = reinterpret_cast<sockaddr_in*>(&src_addr.addr); - - // FIXME (b/137781162): When sending a multicast packet use the proper logic - // to determine the packet's src-IP. - SKIP_IF(IsRunningOnGvisor()); + SyscallSucceedsWithValue(sizeof(send_buf))); + ASSERT_EQ(src_addr.addr_len, sizeof(struct sockaddr_in)); // Verify the received source address. - EXPECT_EQ(eth_if_addr_.sin_addr.s_addr, src_addr_in->sin_addr.s_addr); + // + // TODO(https://gvisor.dev/issue/6686): gVisor is a strong host, preventing + // the packet from being sent from the loopback device using the ethernet + // device's address. + if (IsRunningOnGvisor()) { + EXPECT_EQ(GetAddrStr(AsSockAddr(&src_addr.addr)), + GetAddr4Str(&lo_if_addr().sin_addr)); + } else { + EXPECT_EQ(GetAddrStr(AsSockAddr(&src_addr.addr)), + GetAddr4Str(ð_if_addr().sin_addr)); + } } // Check that when we are bound to one interface we can set IP_MULTICAST_IF to // another interface. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, IpMulticastLoopbackBindToOneIfSetMcastIfToAnother) { - SKIP_IF(!found_net_interfaces_); - - // FIXME (b/137790511): When bound to one interface it is not possible to set - // IP_MULTICAST_IF to a different interface. - SKIP_IF(IsRunningOnGvisor()); - - // Create sender and bind to eth interface. - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - ASSERT_THAT( - bind(sender->get(), AsSockAddr(ð_if_addr_), sizeof(eth_if_addr_)), - SyscallSucceeds()); - // Run through all possible combinations of index and address for // IP_MULTICAST_IF that selects the loopback interface. - struct { - int imr_ifindex; - struct in_addr imr_address; - } test_data[] = { - {lo_if_idx_, {}}, - {0, lo_if_addr_.sin_addr}, - {lo_if_idx_, lo_if_addr_.sin_addr}, - {lo_if_idx_, eth_if_addr_.sin_addr}, + ip_mreqn ifaces[] = { + { + .imr_address = {}, + .imr_ifindex = lo_if_idx(), + }, + { + .imr_address = lo_if_addr().sin_addr, + .imr_ifindex = 0, + }, + { + .imr_address = lo_if_addr().sin_addr, + .imr_ifindex = lo_if_idx(), + }, + { + .imr_address = eth_if_addr().sin_addr, + .imr_ifindex = lo_if_idx(), + }, }; - for (auto t : test_data) { - ip_mreqn iface = {}; - iface.imr_ifindex = t.imr_ifindex; - iface.imr_address = t.imr_address; - EXPECT_THAT(setsockopt(sender->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()) - << "imr_index=" << iface.imr_ifindex - << " imr_address=" << GetAddr4Str(&iface.imr_address); + + { + auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + ASSERT_THAT( + bind(sender->get(), AsSockAddr(ð_if_addr()), sizeof(eth_if_addr())), + SyscallSucceeds()); + + for (const ip_mreqn& iface : ifaces) { + EXPECT_THAT(setsockopt(sender->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, + sizeof(iface)), + SyscallSucceeds()) + << " imr_index=" << iface.imr_ifindex + << " imr_address=" << GetAddr4Str(&iface.imr_address); + } + } + + { + char eth_if_name[IF_NAMESIZE]; + memset(eth_if_name, 0xAA, sizeof(eth_if_name)); + ASSERT_NE(if_indextoname(eth_if_idx(), eth_if_name), nullptr) + << strerror(errno); + auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + ASSERT_THAT(setsockopt(sender->get(), SOL_SOCKET, SO_BINDTODEVICE, + eth_if_name, sizeof(eth_if_name)), + SyscallSucceeds()); + + for (const ip_mreqn& iface : ifaces) { + // FIXME(b/137790511): Disallow mismatching IP_MULTICAST_IF and + // SO_BINDTODEVICE. + if (IsRunningOnGvisor()) { + EXPECT_THAT(setsockopt(sender->get(), IPPROTO_IP, IP_MULTICAST_IF, + &iface, sizeof(iface)), + SyscallSucceeds()) + << " imr_index=" << iface.imr_ifindex + << " imr_address=" << GetAddr4Str(&iface.imr_address); + } else { + EXPECT_THAT(setsockopt(sender->get(), IPPROTO_IP, IP_MULTICAST_IF, + &iface, sizeof(iface)), + SyscallFailsWithErrno(EINVAL)) + << " imr_index=" << iface.imr_ifindex + << " imr_address=" << GetAddr4Str(&iface.imr_address); + } + } } } + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h index 20922ac1f..ac917b32c 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h @@ -22,8 +22,22 @@ namespace testing { // Test fixture for tests that apply to unbound IPv4 UDP sockets in a sandbox // with external networking support. -using IPv4UDPUnboundExternalNetworkingSocketTest = - IPUDPUnboundExternalNetworkingSocketTest; +class IPv4UDPUnboundExternalNetworkingSocketTest + : public IPUDPUnboundExternalNetworkingSocketTest { + protected: + void SetUp() override; + + int lo_if_idx() const { return std::get<0>(lo_if_.value()); } + int eth_if_idx() const { return std::get<0>(eth_if_.value()); } + + const sockaddr_in& lo_if_addr() const { return std::get<1>(lo_if_.value()); } + const sockaddr_in& eth_if_addr() const { + return std::get<1>(eth_if_.value()); + } + + private: + std::optional<std::pair<int, sockaddr_in>> lo_if_, eth_if_; +}; } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc index d72b6b53f..c6e563cd3 100644 --- a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc +++ b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc @@ -18,8 +18,6 @@ namespace gvisor { namespace testing { TEST_P(IPv6UDPUnboundExternalNetworkingSocketTest, TestJoinLeaveMulticast) { - SKIP_IF(!found_net_interfaces_); - auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); diff --git a/test/util/socket_util.h b/test/util/socket_util.h index 0e2be63cc..588f041b7 100644 --- a/test/util/socket_util.h +++ b/test/util/socket_util.h @@ -554,15 +554,27 @@ uint16_t ICMPChecksum(struct icmphdr icmphdr, const char* payload, inline sockaddr* AsSockAddr(sockaddr_storage* s) { return reinterpret_cast<sockaddr*>(s); } +inline const sockaddr* AsSockAddr(const sockaddr_storage* s) { + return reinterpret_cast<const sockaddr*>(s); +} inline sockaddr* AsSockAddr(sockaddr_in* s) { return reinterpret_cast<sockaddr*>(s); } +inline const sockaddr* AsSockAddr(const sockaddr_in* s) { + return reinterpret_cast<const sockaddr*>(s); +} inline sockaddr* AsSockAddr(sockaddr_in6* s) { return reinterpret_cast<sockaddr*>(s); } +inline const sockaddr* AsSockAddr(const sockaddr_in6* s) { + return reinterpret_cast<const sockaddr*>(s); +} inline sockaddr* AsSockAddr(sockaddr_un* s) { return reinterpret_cast<sockaddr*>(s); } +inline const sockaddr* AsSockAddr(const sockaddr_un* s) { + return reinterpret_cast<const sockaddr*>(s); +} PosixErrorOr<uint16_t> AddrPort(int family, sockaddr_storage const& addr); |