diff options
Diffstat (limited to 'test')
70 files changed, 3732 insertions, 1882 deletions
diff --git a/test/benchmarks/base/startup_test.go b/test/benchmarks/base/startup_test.go index 05a43ad17..197241622 100644 --- a/test/benchmarks/base/startup_test.go +++ b/test/benchmarks/base/startup_test.go @@ -34,15 +34,19 @@ func BenchmarkStartupEmpty(b *testing.B) { defer machine.CleanUp() ctx := context.Background() + b.StopTimer() + b.ResetTimer() for i := 0; i < b.N; i++ { harness.DebugLog(b, "Running container: %d", i) container := machine.GetContainer(ctx, b) - defer container.CleanUp(ctx) - if _, err := container.Run(ctx, dockerutil.RunOpts{ + b.StartTimer() + if err := container.Spawn(ctx, dockerutil.RunOpts{ Image: "benchmarks/alpine", - }, "true"); err != nil { - b.Fatalf("failed to run container: %v", err) + }, "sleep", "100"); err != nil { + b.Fatalf("failed to start container: %v", err) } + b.StopTimer() + container.CleanUp(ctx) harness.DebugLog(b, "Ran container: %d", i) } } diff --git a/test/benchmarks/tcp/tcp_proxy.go b/test/benchmarks/tcp/tcp_proxy.go index be74e4d4a..bc0d8fd38 100644 --- a/test/benchmarks/tcp/tcp_proxy.go +++ b/test/benchmarks/tcp/tcp_proxy.go @@ -208,8 +208,12 @@ func newNetstackImpl(mode string) (impl, error) { if err := s.CreateNIC(nicID, fifo.New(ep, runtime.GOMAXPROCS(0), 1000)); err != nil { return nil, fmt.Errorf("error creating NIC %q: %v", *iface, err) } - if err := s.AddAddress(nicID, ipv4.ProtocolNumber, parsedAddr); err != nil { - return nil, fmt.Errorf("error adding IP address to %q: %v", *iface, err) + protocolAddr := tcpip.ProtocolAddress{ + Protocol: ipv4.ProtocolNumber, + AddressWithPrefix: parsedAddr.WithPrefix(), + } + if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { + return nil, fmt.Errorf("error adding IP address %+v to %q: %w", protocolAddr, *iface, err) } subnet, err := tcpip.NewSubnet(parsedDest, parsedMask) diff --git a/test/benchmarks/tools/fio_test.go b/test/benchmarks/tools/fio_test.go index a98277150..3b1f852ce 100644 --- a/test/benchmarks/tools/fio_test.go +++ b/test/benchmarks/tools/fio_test.go @@ -86,7 +86,7 @@ func TestFio(t *testing.T) { fio := Fio{} // WriteBandwidth. got, err := fio.parseBandwidth(sampleData, false) - var want float64 = 1753471.0 * 1024 + want := 1753471.0 * 1024 if err != nil { t.Fatalf("parse failed with err: %v", err) } else if got != want { diff --git a/test/packetimpact/README.md b/test/packetimpact/README.md index fe0976ba5..3f0f9d155 100644 --- a/test/packetimpact/README.md +++ b/test/packetimpact/README.md @@ -16,7 +16,7 @@ Packetimpact aims to provide: * A **multi-platform** solution that can test both Linux and gVisor. * **Conciseness** on par with packetdrill scripts. * **Control-flow** like for loops, conditionals, and variables. -* **Flexibilty** to specify every byte in a packet or use multiple sockets. +* **Flexibility** to specify every byte in a packet or use multiple sockets. ## How to run packetimpact tests? @@ -177,7 +177,7 @@ message SocketResponse { The test bench does most of the work in a test. It is a Go program that compiles on the host and is copied by the script into test bench's container. It is a regular [go unit test](https://golang.org/pkg/testing/) that imports the test -bench framework. The test bench framwork is based on three basic utilities: +bench framework. The test bench framework is based on three basic utilities: * Commanding the DUT to run POSIX commands and return responses. * Sending raw packets to the DUT on the test network. @@ -475,9 +475,9 @@ type layerState interface { // as it was sent is available. sent(sent Layer) error - // received updates the layerState based on a Layer that is receieved. The + // received updates the layerState based on a Layer that is received. The // input is a Layer with all prev and next pointers populated so that the - // entire frame as it was receieved is available. + // entire frame as it was received is available. received(received Layer) error // close frees associated resources held by the LayerState. diff --git a/test/packetimpact/dut/posix_server.cc b/test/packetimpact/dut/posix_server.cc index ea83bbe72..49f41c887 100644 --- a/test/packetimpact/dut/posix_server.cc +++ b/test/packetimpact/dut/posix_server.cc @@ -28,10 +28,10 @@ #include <iostream> #include <unordered_map> +#include "absl/strings/str_format.h" #include "include/grpcpp/security/server_credentials.h" #include "include/grpcpp/server_builder.h" #include "include/grpcpp/server_context.h" -#include "absl/strings/str_format.h" #include "test/packetimpact/proto/posix_server.grpc.pb.h" #include "test/packetimpact/proto/posix_server.pb.h" 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/packetimpact/tests/udp_icmp_error_propagation_test.go b/test/packetimpact/tests/udp_icmp_error_propagation_test.go index bb33ca4b3..556cee1d9 100644 --- a/test/packetimpact/tests/udp_icmp_error_propagation_test.go +++ b/test/packetimpact/tests/udp_icmp_error_propagation_test.go @@ -155,7 +155,7 @@ func testRecv(ctx context.Context, t *testing.T, d testData) { // testSendTo tests observing the ICMP error through the send syscall. If // wantErrno is non-zero, the first send should fail and a subsequent send -// should suceed; while if wantErrno is zero then the first send should just +// should succeed; while if wantErrno is zero then the first send should just // succeed. func testSendTo(ctx context.Context, t *testing.T, d testData) { // Check that sending on the clean socket works. diff --git a/test/perf/BUILD b/test/perf/BUILD index 58fe333ee..59923da47 100644 --- a/test/perf/BUILD +++ b/test/perf/BUILD @@ -174,3 +174,10 @@ syscall_test( test = "//test/perf/linux:verity_open_read_close_benchmark", vfs1 = False, ) + +syscall_test( + size = "large", + debug = False, + test = "//test/perf/linux:verity_stat_benchmark", + vfs1 = False, +) diff --git a/test/perf/linux/BUILD b/test/perf/linux/BUILD index 61ed98ff5..020a69ab5 100644 --- a/test/perf/linux/BUILD +++ b/test/perf/linux/BUILD @@ -462,3 +462,23 @@ cc_binary( "//test/util:verity_util", ], ) + +cc_binary( + name = "verity_stat_benchmark", + testonly = 1, + srcs = [ + "verity_stat_benchmark.cc", + ], + deps = [ + gbenchmark, + gtest, + "//test/util:capability_util", + "//test/util:fs_util", + "//test/util:logging", + "//test/util:temp_path", + "//test/util:test_main", + "//test/util:test_util", + "//test/util:verity_util", + "@com_google_absl//absl/strings", + ], +) diff --git a/test/perf/linux/randread_benchmark.cc b/test/perf/linux/randread_benchmark.cc index b0eb8c24e..11b56a8cb 100644 --- a/test/perf/linux/randread_benchmark.cc +++ b/test/perf/linux/randread_benchmark.cc @@ -85,7 +85,7 @@ void BM_RandRead(benchmark::State& state) { unsigned int seed = 1; for (auto _ : state) { TEST_CHECK(PreadFd(fd.get(), buf.data(), buf.size(), - rand_r(&seed) % kFileSize) == size); + rand_r(&seed) % (kFileSize - buf.size())) == size); } state.SetBytesProcessed(static_cast<int64_t>(size) * diff --git a/test/perf/linux/verity_stat_benchmark.cc b/test/perf/linux/verity_stat_benchmark.cc new file mode 100644 index 000000000..b43a9e266 --- /dev/null +++ b/test/perf/linux/verity_stat_benchmark.cc @@ -0,0 +1,84 @@ +// 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. + +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <algorithm> +#include <vector> + +#include "gtest/gtest.h" +#include "absl/strings/str_cat.h" +#include "benchmark/benchmark.h" +#include "test/util/fs_util.h" +#include "test/util/temp_path.h" +#include "test/util/test_util.h" +#include "test/util/verity_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +// Creates a file in a nested directory hierarchy at least `depth` directories +// deep, and stats that file multiple times. +void BM_VerityStat(benchmark::State& state) { + // Create nested directories with given depth. + int depth = state.range(0); + + // Mount a tmpfs file system to be wrapped by a verity fs. + TempPath top_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); + TEST_CHECK(mount("", top_dir.path().c_str(), "tmpfs", 0, "") == 0); + std::string dir_path = top_dir.path(); + std::string child_path = ""; + std::vector<EnableTarget> targets; + + while (depth-- > 0) { + // Don't use TempPath because it will make paths too long to use. + // + // The top_dir destructor will clean up this whole tree. + dir_path = JoinPath(dir_path, absl::StrCat(depth)); + ASSERT_NO_ERRNO(Mkdir(dir_path, 0755)); + child_path = JoinPath(child_path, Basename(dir_path)); + targets.emplace_back(EnableTarget(child_path, O_RDONLY)); + } + + // Create the file that will be stat'd. + const TempPath file = + ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir_path)); + + targets.emplace_back( + EnableTarget(JoinPath(child_path, Basename(file.path())), O_RDONLY)); + + // Reverse the targets because verity should be enabled from the lowest level. + std::reverse(targets.begin(), targets.end()); + + std::string verity_dir = + TEST_CHECK_NO_ERRNO_AND_VALUE(MountVerity(top_dir.path(), targets)); + + struct stat st; + for (auto _ : state) { + ASSERT_THAT(stat(JoinPath(verity_dir, targets[0].path).c_str(), &st), + SyscallSucceeds()); + } +} + +BENCHMARK(BM_VerityStat)->Range(1, 100)->UseRealTime(); + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/runner/gtest/gtest.go b/test/runner/gtest/gtest.go index 38e57d62f..affbf1973 100644 --- a/test/runner/gtest/gtest.go +++ b/test/runner/gtest/gtest.go @@ -133,17 +133,28 @@ func ParseTestCases(testBin string, benchmarks bool, extraArgs ...string) ([]Tes } // Run again to extract benchmarks. - args = append([]string{listBenchmarkFlag}, extraArgs...) - cmd = exec.Command(testBin, args...) - out, err = cmd.Output() + tb, err := ParseBenchmarks(testBin, extraArgs...) + if err != nil { + return nil, err + } + t = append(t, tb...) + return t, nil +} + +// ParseBenchmarks returns each benchmark in a third_party/benchmark binary's list as a single test case. +func ParseBenchmarks(binary string, extraArgs ...string) ([]TestCase, error) { + var t []TestCase + args := append([]string{listBenchmarkFlag}, extraArgs...) + cmd := exec.Command(binary, args...) + out, err := cmd.Output() if err != nil { // We were able to enumerate tests above, but not benchmarks? // We requested them, so we return an error in this case. exitErr, ok := err.(*exec.ExitError) if !ok { - return nil, fmt.Errorf("could not enumerate gtest benchmarks: %v", err) + return nil, fmt.Errorf("could not enumerate benchmarks: %v", err) } - return nil, fmt.Errorf("could not enumerate gtest benchmarks: %v\nstderr\n%s", err, exitErr.Stderr) + return nil, fmt.Errorf("could not enumerate benchmarks: %v\nstderr\n%s", err, exitErr.Stderr) } benches := strings.Trim(string(out), "\n") diff --git a/test/runner/main.go b/test/runner/main.go index 34e9c6279..2cab8d2d4 100644 --- a/test/runner/main.go +++ b/test/runner/main.go @@ -170,6 +170,7 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error { "-network", *network, "-log-format=text", "-TESTONLY-unsafe-nonroot=true", + "-TESTONLY-allow-packet-endpoint-write=true", "-net-raw=true", fmt.Sprintf("-panic-signal=%d", unix.SIGTERM), "-watchdog-action=panic", 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/BUILD b/test/syscalls/BUILD index 16c451786..168766dd8 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -359,6 +359,10 @@ syscall_test( ) syscall_test( + test = "//test/syscalls/linux:packet_socket_dgram_test", +) + +syscall_test( test = "//test/syscalls/linux:packet_socket_raw_test", ) @@ -430,6 +434,11 @@ syscall_test( ) syscall_test( + tags = ["container"], + test = "//test/syscalls/linux:proc_isolated_test", +) + +syscall_test( test = "//test/syscalls/linux:proc_net_test", ) @@ -707,6 +716,11 @@ syscall_test( syscall_test( size = "medium", + test = "//test/syscalls/linux:socket_ipv4_datagram_based_socket_unbound_loopback_test", +) + +syscall_test( + size = "medium", test = "//test/syscalls/linux:socket_ipv4_udp_unbound_loopback_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 9c7bb8394..45f6304e5 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -7,6 +7,12 @@ package( exports_files( [ + "packet_socket.cc", + "packet_socket_dgram.cc", + "packet_socket_raw.cc", + "raw_socket.cc", + "raw_socket_hdrincl.cc", + "raw_socket_icmp.cc", "socket.cc", "socket_inet_loopback.cc", "socket_inet_loopback_isolated.cc", @@ -21,6 +27,7 @@ exports_files( "socket_ip_udp_loopback_blocking.cc", "socket_ip_udp_loopback_nonblock.cc", "socket_ip_unbound.cc", + "socket_ipv4_datagram_based_socket_unbound_loopback.cc", "socket_ipv4_udp_unbound_external_networking_test.cc", "socket_ipv6_udp_unbound_external_networking_test.cc", "socket_ipv4_udp_unbound_loopback.cc", @@ -581,6 +588,7 @@ cc_binary( "//test/util:file_descriptor", gtest, "//test/util:posix_error", + "//test/util:socket_util", "//test/util:test_main", "//test/util:test_util", "//test/util:thread_util", @@ -1436,14 +1444,15 @@ cc_binary( ) cc_binary( - name = "packet_socket_raw_test", + name = "packet_socket_dgram_test", testonly = 1, - srcs = ["packet_socket_raw.cc"], - defines = select_system(), + srcs = ["packet_socket_dgram.cc"], linkstatic = 1, deps = [ + ":ip_socket_test_util", ":unix_domain_socket_test_util", "//test/util:capability_util", + "//test/util:cleanup", "//test/util:file_descriptor", "//test/util:socket_util", "@com_google_absl//absl/base:core_headers", @@ -1455,13 +1464,15 @@ cc_binary( ) cc_binary( - name = "packet_socket_test", + name = "packet_socket_raw_test", testonly = 1, - srcs = ["packet_socket.cc"], + srcs = ["packet_socket_raw.cc"], linkstatic = 1, deps = [ + ":ip_socket_test_util", ":unix_domain_socket_test_util", "//test/util:capability_util", + "//test/util:cleanup", "//test/util:file_descriptor", "//test/util:socket_util", "@com_google_absl//absl/base:core_headers", @@ -1473,6 +1484,21 @@ cc_binary( ) cc_binary( + name = "packet_socket_test", + testonly = 1, + srcs = ["packet_socket.cc"], + linkstatic = 1, + deps = [ + ":ip_socket_test_util", + "//test/util:capability_util", + "//test/util:file_descriptor", + "//test/util:socket_util", + gtest, + "//test/util:test_main", + ], +) + +cc_binary( name = "pty_test", testonly = 1, srcs = ["pty.cc"], @@ -1769,6 +1795,20 @@ cc_binary( ) cc_binary( + name = "proc_isolated_test", + testonly = 1, + srcs = ["proc_isolated.cc"], + linkstatic = 1, + deps = [ + "@com_google_absl//absl/strings", + gtest, + "//test/util:fs_util", + "//test/util:test_main", + "//test/util:test_util", + ], +) + +cc_binary( name = "proc_net_test", testonly = 1, srcs = ["proc_net.cc"], @@ -2526,6 +2566,24 @@ cc_library( ) cc_library( + name = "socket_ipv4_datagram_based_socket_unbound_test_cases", + testonly = 1, + srcs = [ + "socket_ipv4_datagram_based_socket_unbound.cc", + ], + hdrs = [ + "socket_ipv4_datagram_based_socket_unbound.h", + ], + deps = [ + ":ip_socket_test_util", + "//test/util:capability_util", + "//test/util:socket_util", + gtest, + ], + alwayslink = 1, +) + +cc_library( name = "socket_ipv4_udp_unbound_test_cases", testonly = 1, srcs = [ @@ -2539,9 +2597,6 @@ cc_library( "//test/util:socket_util", "@com_google_absl//absl/memory", gtest, - "//test/util:posix_error", - "//test/util:save_util", - "//test/util:test_util", ], alwayslink = 1, ) @@ -2607,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, ) @@ -2632,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, @@ -2948,9 +3001,21 @@ cc_binary( deps = [ ":ip_socket_test_util", ":socket_ipv4_udp_unbound_test_cases", - "//test/util:socket_util", "//test/util:test_main", - "//test/util:test_util", + ], +) + +cc_binary( + name = "socket_ipv4_datagram_based_socket_unbound_loopback_test", + testonly = 1, + srcs = [ + "socket_ipv4_datagram_based_socket_unbound_loopback.cc", + ], + linkstatic = 1, + deps = [ + ":ip_socket_test_util", + ":socket_ipv4_datagram_based_socket_unbound_test_cases", + "//test/util:test_main", ], ) @@ -3287,9 +3352,12 @@ cc_library( ], deps = [ ":unix_domain_socket_test_util", + "//test/util:file_descriptor", + "//test/util:memory_util", "//test/util:socket_util", "@com_google_absl//absl/strings", gtest, + "//test/util:temp_path", "//test/util:test_util", "//test/util:thread_util", ], @@ -4123,12 +4191,10 @@ cc_binary( srcs = ["network_namespace.cc"], linkstatic = 1, deps = [ - "//test/util:socket_util", gtest, + ":ip_socket_test_util", "//test/util:capability_util", - "//test/util:posix_error", "//test/util:test_main", - "//test/util:test_util", "//test/util:thread_util", ], ) diff --git a/test/syscalls/linux/cgroup.cc b/test/syscalls/linux/cgroup.cc index f29891571..ca23dfeee 100644 --- a/test/syscalls/linux/cgroup.cc +++ b/test/syscalls/linux/cgroup.cc @@ -279,6 +279,23 @@ TEST(Cgroup, UnmountRepeated) { EXPECT_THAT(umount(c.Path().c_str()), SyscallFailsWithErrno(EINVAL)); } +TEST(Cgroup, Create) { + SKIP_IF(!CgroupsAvailable()); + Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); + Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("")); + ASSERT_NO_ERRNO(c.CreateChild("child1")); + EXPECT_TRUE(ASSERT_NO_ERRNO_AND_VALUE(Exists(c.Path()))); +} + +TEST(Cgroup, SubcontainerInitiallyEmpty) { + SKIP_IF(!CgroupsAvailable()); + Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); + Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("")); + Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child1")); + auto procs = ASSERT_NO_ERRNO_AND_VALUE(child.Procs()); + EXPECT_TRUE(procs.empty()); +} + TEST(MemoryCgroup, MemoryUsageInBytes) { SKIP_IF(!CgroupsAvailable()); diff --git a/test/syscalls/linux/epoll.cc b/test/syscalls/linux/epoll.cc index 89b16d168..600a40ef8 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 { @@ -497,47 +498,39 @@ TEST(EpollTest, PipeReaderHupAfterWriterClosed) { } TEST(EpollTest, DoubleLayerEpoll) { - int listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - struct sockaddr_in sa; - sa.sin_family = AF_INET; - inet_aton("127.0.0.1", &sa.sin_addr); - sa.sin_port = htons(8888); - bind(listen_fd, (struct sockaddr *)&sa, sizeof(sa)); - listen(listen_fd, 1); + DisableSave ds; + int pipefds[2]; + ASSERT_THAT(pipe(pipefds), SyscallSucceeds()); + FileDescriptor rfd(pipefds[0]); + FileDescriptor wfd(pipefds[1]); auto epfd1 = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); - ASSERT_NO_ERRNO(RegisterEpollFD(epfd1.get(), listen_fd, EPOLLIN|EPOLLHUP, listen_fd)); + 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())); - - struct epoll_event ret_events[2]; - - // first time - ScopedThread thread1([&sa]() { - sleep(1); - int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - ASSERT_THAT(connect(sock, (struct sockaddr *)&sa, sizeof(sa)), SyscallSucceeds()); - }); - ASSERT_THAT(epoll_wait(epfd2.get(), ret_events, 2, 2000), SyscallSucceedsWithValue(1)); - ASSERT_EQ(ret_events[0].data.fd, epfd1.get()); - ASSERT_THAT(epoll_wait(epfd1.get(), ret_events, 2, 2000), SyscallSucceedsWithValue(1)); - ASSERT_EQ(ret_events[0].data.fd, listen_fd); - int server_fd = accept(listen_fd, NULL, NULL); - ASSERT_THAT(close(server_fd), SyscallSucceeds()); - - // second time to check if event in epfd1 is cleaned correctly - ScopedThread thread2([&sa]() { - sleep(1); - int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - ASSERT_THAT(connect(sock, (struct sockaddr *)&sa, sizeof(sa)), SyscallSucceeds()); - }); - ASSERT_THAT(epoll_wait(epfd2.get(), ret_events, 2, 2000), SyscallSucceedsWithValue(1)); - ASSERT_EQ(ret_events[0].data.fd, epfd1.get()); - ASSERT_THAT(epoll_wait(epfd1.get(), ret_events, 2, 2000), SyscallSucceedsWithValue(1)); - ASSERT_EQ(ret_events[0].data.fd, listen_fd); - server_fd = accept(listen_fd, NULL, NULL); - ASSERT_THAT(close(server_fd), SyscallSucceeds()); + 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, 2000), + SyscallSucceedsWithValue(1)); + ASSERT_EQ(ret_events[0].data.fd, epfd1.get()); + ASSERT_THAT(RetryEINTR(epoll_wait)(epfd1.get(), ret_events, 2, 2000), + 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 diff --git a/test/syscalls/linux/eventfd.cc b/test/syscalls/linux/eventfd.cc index 8202d35fa..a2cc59e83 100644 --- a/test/syscalls/linux/eventfd.cc +++ b/test/syscalls/linux/eventfd.cc @@ -149,31 +149,6 @@ TEST(EventfdTest, BigWriteBigRead) { EXPECT_EQ(l[0], 1); } -TEST(EventfdTest, SpliceFromPipePartialSucceeds) { - int pipes[2]; - ASSERT_THAT(pipe2(pipes, O_NONBLOCK), SyscallSucceeds()); - const FileDescriptor pipe_rfd(pipes[0]); - const FileDescriptor pipe_wfd(pipes[1]); - constexpr uint64_t kVal{1}; - - FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK)); - - uint64_t event_array[2]; - event_array[0] = kVal; - event_array[1] = kVal; - ASSERT_THAT(write(pipe_wfd.get(), event_array, sizeof(event_array)), - SyscallSucceedsWithValue(sizeof(event_array))); - EXPECT_THAT(splice(pipe_rfd.get(), /*__offin=*/nullptr, efd.get(), - /*__offout=*/nullptr, sizeof(event_array[0]) + 1, - SPLICE_F_NONBLOCK), - SyscallSucceedsWithValue(sizeof(event_array[0]))); - - uint64_t val; - ASSERT_THAT(read(efd.get(), &val, sizeof(val)), - SyscallSucceedsWithValue(sizeof(val))); - EXPECT_EQ(val, kVal); -} - // NotifyNonZero is inherently racy, so random save is disabled. TEST(EventfdTest, NotifyNonZero) { // Waits will time out at 10 seconds. diff --git a/test/syscalls/linux/inotify.cc b/test/syscalls/linux/inotify.cc index f6b78989b..e2622232d 100644 --- a/test/syscalls/linux/inotify.cc +++ b/test/syscalls/linux/inotify.cc @@ -1849,34 +1849,6 @@ TEST(Inotify, SpliceOnWatchTarget) { })); } -TEST(Inotify, SpliceOnInotifyFD) { - int pipefds[2]; - ASSERT_THAT(pipe2(pipefds, O_NONBLOCK), SyscallSucceeds()); - - const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); - const FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)); - const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - root.path(), "some content", TempPath::kDefaultFileMode)); - - const FileDescriptor file1_fd = - ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY)); - const int watcher = ASSERT_NO_ERRNO_AND_VALUE( - InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS)); - - char buf; - EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds()); - - EXPECT_THAT(splice(fd.get(), nullptr, pipefds[1], nullptr, - sizeof(struct inotify_event) + 1, SPLICE_F_NONBLOCK), - SyscallSucceedsWithValue(sizeof(struct inotify_event))); - - const FileDescriptor read_fd(pipefds[0]); - const std::vector<Event> events = - ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(read_fd.get())); - ASSERT_THAT(events, Are({Event(IN_ACCESS, watcher)})); -} - // Watches on a parent should not be triggered by actions on a hard link to one // of its children that has a different parent. TEST(Inotify, LinkOnOtherParent) { diff --git a/test/syscalls/linux/ip_socket_test_util.cc b/test/syscalls/linux/ip_socket_test_util.cc index e90a7e411..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> @@ -156,6 +157,14 @@ SocketKind ICMPv6UnboundSocket(int type) { UnboundSocketCreator(AF_INET6, type | SOCK_DGRAM, IPPROTO_ICMPV6)}; } +SocketKind IPv4RawUDPUnboundSocket(int type) { + std::string description = + absl::StrCat(DescribeSocketType(type), "IPv4 Raw UDP socket"); + return SocketKind{ + description, AF_INET, type | SOCK_RAW, IPPROTO_UDP, + UnboundSocketCreator(AF_INET, type | SOCK_RAW, IPPROTO_UDP)}; +} + SocketKind IPv4UDPUnboundSocket(int type) { std::string description = absl::StrCat(DescribeSocketType(type), "IPv4 UDP socket"); @@ -188,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 8f26f1cd0..957006e25 100644 --- a/test/syscalls/linux/ip_socket_test_util.h +++ b/test/syscalls/linux/ip_socket_test_util.h @@ -35,6 +35,9 @@ uint16_t PortFromInetSockaddr(const struct sockaddr* addr); // InterfaceIndex returns the index of the named interface. PosixErrorOr<int> InterfaceIndex(std::string name); +// GetLoopbackIndex returns the index of the loopback interface. +inline PosixErrorOr<int> GetLoopbackIndex() { return InterfaceIndex("lo"); } + // IPv6TCPAcceptBindSocketPair returns a SocketPairKind that represents // SocketPairs created with bind() and accept() syscalls with AF_INET6 and the // given type bound to the IPv6 loopback. @@ -92,6 +95,10 @@ SocketKind ICMPUnboundSocket(int type); // created with AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6, and the given type. SocketKind ICMPv6UnboundSocket(int type); +// IPv4RawUDPUnboundSocket returns a SocketKind that represents a SimpleSocket +// created with AF_INET, SOCK_RAW, IPPROTO_UDP, and the given type. +SocketKind IPv4RawUDPUnboundSocket(int type); + // IPv4UDPUnboundSocket returns a SocketKind that represents a SimpleSocket // created with AF_INET, SOCK_DGRAM, IPPROTO_UDP, and the given type. SocketKind IPv4UDPUnboundSocket(int type); @@ -108,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/link.cc b/test/syscalls/linux/link.cc index 4f9ca1a65..8b208f99a 100644 --- a/test/syscalls/linux/link.cc +++ b/test/syscalls/linux/link.cc @@ -142,7 +142,8 @@ TEST(LinkTest, OldnameIsEmpty) { TEST(LinkTest, OldnameDoesNotExist) { const std::string oldname = NewTempAbsPath(); const std::string newname = NewTempAbsPath(); - EXPECT_THAT(link("", newname.c_str()), SyscallFailsWithErrno(ENOENT)); + EXPECT_THAT(link(oldname.c_str(), newname.c_str()), + SyscallFailsWithErrno(ENOENT)); } TEST(LinkTest, NewnameCannotExist) { diff --git a/test/syscalls/linux/mmap.cc b/test/syscalls/linux/mmap.cc index caad575cf..bb2a0cb57 100644 --- a/test/syscalls/linux/mmap.cc +++ b/test/syscalls/linux/mmap.cc @@ -29,6 +29,7 @@ #include <sys/wait.h> #include <unistd.h> +#include <limits> #include <vector> #include "gmock/gmock.h" @@ -795,7 +796,7 @@ class MMapFileTest : public MMapTest { bool FSSupportsMap() const { bool supported = true; - void* ret = mmap(nullptr, 1, PROT_NONE, 0, fd_.get(), 0); + void* ret = mmap(nullptr, 1, PROT_NONE, MAP_PRIVATE, fd_.get(), 0); if (ret == MAP_FAILED && errno != ENODEV) { supported = false; } @@ -913,13 +914,41 @@ TEST_F(MMapFileTest, MapOffsetBeyondEnd) { ::testing::KilledBySignal(SIGBUS), ""); } -// Verify mmap fails when sum of length and offset overflows. -TEST_F(MMapFileTest, MapLengthPlusOffsetOverflows) { +TEST_F(MMapFileTest, MapSecondToLastPositivePage) { SKIP_IF(!FSSupportsMap()); - const size_t length = static_cast<size_t>(-kPageSize); - const off_t offset = kPageSize; - ASSERT_THAT(Map(0, length, PROT_READ, MAP_PRIVATE, fd_.get(), offset), - SyscallFailsWithErrno(ENOMEM)); + EXPECT_THAT( + Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), + (std::numeric_limits<off_t>::max() - kPageSize) & ~(kPageSize - 1)), + SyscallSucceeds()); +} + +TEST_F(MMapFileTest, MapLastPositivePage) { + SKIP_IF(!FSSupportsMap()); + // For regular files, this should fail due to integer overflow of the end + // offset. + EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), + std::numeric_limits<off_t>::max() & ~(kPageSize - 1)), + SyscallFailsWithErrno(EOVERFLOW)); +} + +TEST_F(MMapFileTest, MapFirstNegativePage) { + SKIP_IF(!FSSupportsMap()); + EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), + std::numeric_limits<off_t>::min()), + SyscallFailsWithErrno(EOVERFLOW)); +} + +TEST_F(MMapFileTest, MapSecondToLastNegativePage) { + SKIP_IF(!FSSupportsMap()); + EXPECT_THAT( + Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), -(2 * kPageSize)), + SyscallFailsWithErrno(EOVERFLOW)); +} + +TEST_F(MMapFileTest, MapLastNegativePage) { + SKIP_IF(!FSSupportsMap()); + EXPECT_THAT(Map(0, kPageSize, PROT_READ, MAP_SHARED, fd_.get(), -kPageSize), + SyscallFailsWithErrno(EOVERFLOW)); } // MAP_PRIVATE PROT_WRITE is allowed on read-only FDs. diff --git a/test/syscalls/linux/msgqueue.cc b/test/syscalls/linux/msgqueue.cc index 8994b739a..c4761eba8 100644 --- a/test/syscalls/linux/msgqueue.cc +++ b/test/syscalls/linux/msgqueue.cc @@ -30,9 +30,15 @@ namespace gvisor { namespace testing { namespace { -constexpr int msgMax = 8192; // Max size for message in bytes. +// Source: include/uapi/linux/msg.h +constexpr int msgMnb = 16384; // Maximum number of bytes in a queue. constexpr int msgMni = 32000; // Max number of identifiers. -constexpr int msgMnb = 16384; // Default max size of message queue in bytes. +constexpr int msgPool = + (msgMni * msgMnb / 1024); // Size of buffer pool used to hold message data. +constexpr int msgMap = msgMnb; // Maximum number of entries in message map. +constexpr int msgMax = 8192; // Maximum number of bytes in a single message. +constexpr int msgSsz = 16; // Message segment size. +constexpr int msgTql = msgMnb; // Maximum number of messages on all queues. constexpr int kInterruptSignal = SIGALRM; @@ -40,6 +46,10 @@ constexpr int kInterruptSignal = SIGALRM; class Queue { public: explicit Queue(int id) : id_(id) {} + Queue(const Queue&) = delete; + Queue& operator=(const Queue&) = delete; + + Queue(Queue&& other) { id_ = other.release(); } ~Queue() { if (id_ >= 0) { @@ -59,6 +69,14 @@ class Queue { int id_ = -1; }; +PosixErrorOr<Queue> Msgget(key_t key, int flags) { + int id = msgget(key, flags); + if (id == -1) { + return PosixError(errno, absl::StrFormat("msgget(%d, %d)", key, flags)); + } + return Queue(id); +} + // Default size for messages. constexpr size_t msgSize = 50; @@ -90,8 +108,7 @@ TEST(MsgqueueTest, MsgGet) { const key_t key = ftok(keyfile.path().c_str(), 1); ASSERT_THAT(key, SyscallSucceeds()); - Queue queue(msgget(key, IPC_CREAT)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(key, IPC_CREAT)); EXPECT_THAT(msgget(key, 0), SyscallSucceedsWithValue(queue.get())); } @@ -103,27 +120,20 @@ TEST(MsgqueueTest, MsgGetFail) { EXPECT_THAT(msgget(key, 0), SyscallFailsWithErrno(ENOENT)); - Queue queue(msgget(key, IPC_CREAT)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); - + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(key, IPC_CREAT)); EXPECT_THAT(msgget(key, IPC_CREAT | IPC_EXCL), SyscallFailsWithErrno(EEXIST)); } // Test using msgget(2) with IPC_PRIVATE option. TEST(MsgqueueTest, MsgGetIpcPrivate) { - Queue queue1(msgget(IPC_PRIVATE, 0)); - ASSERT_THAT(queue1.get(), SyscallSucceeds()); - - Queue queue2(msgget(IPC_PRIVATE, 0)); - ASSERT_THAT(queue2.get(), SyscallSucceeds()); - + Queue queue1 = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0)); + Queue queue2 = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0)); EXPECT_NE(queue1.get(), queue2.get()); } // Test simple msgsnd and msgrcv. TEST(MsgqueueTest, MsgOpSimple) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, "A message."}; msgbuf rcv; @@ -138,8 +148,7 @@ TEST(MsgqueueTest, MsgOpSimple) { // Test msgsnd and msgrcv of an empty message. TEST(MsgqueueTest, MsgOpEmpty) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, ""}; msgbuf rcv; @@ -151,8 +160,7 @@ TEST(MsgqueueTest, MsgOpEmpty) { // Test truncation of message with MSG_NOERROR flag. TEST(MsgqueueTest, MsgOpTruncate) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, ""}; msgbuf rcv; @@ -166,8 +174,7 @@ TEST(MsgqueueTest, MsgOpTruncate) { // Test msgsnd and msgrcv using invalid arguments. TEST(MsgqueueTest, MsgOpInvalidArgs) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, ""}; @@ -184,8 +191,7 @@ TEST(MsgqueueTest, MsgOpInvalidArgs) { // Test non-blocking msgrcv with an empty queue. TEST(MsgqueueTest, MsgOpNoMsg) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf rcv; EXPECT_THAT(msgrcv(queue.get(), &rcv, sizeof(rcv.mtext) + 1, 0, IPC_NOWAIT), @@ -195,8 +201,7 @@ TEST(MsgqueueTest, MsgOpNoMsg) { // Test non-blocking msgrcv with a non-empty queue, but no messages of wanted // type. TEST(MsgqueueTest, MsgOpNoMsgType) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, ""}; ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0), @@ -208,8 +213,7 @@ TEST(MsgqueueTest, MsgOpNoMsgType) { // Test msgrcv with a larger size message than wanted, and truncation disabled. TEST(MsgqueueTest, MsgOpTooBig) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, ""}; ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0), @@ -221,8 +225,7 @@ TEST(MsgqueueTest, MsgOpTooBig) { // Test receiving messages based on type. TEST(MsgqueueTest, MsgRcvType) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); // Send messages in an order and receive them in reverse, based on type, // which shouldn't block. @@ -248,8 +251,7 @@ TEST(MsgqueueTest, MsgRcvType) { // Test using MSG_EXCEPT to receive a different-type message. TEST(MsgqueueTest, MsgExcept) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); std::map<int64_t, msgbuf> typeToBuf = { {1, msgbuf{1, "Message 1."}}, @@ -274,8 +276,7 @@ TEST(MsgqueueTest, MsgExcept) { // Test msgrcv with a negative type. TEST(MsgqueueTest, MsgRcvTypeNegative) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); // When msgtyp is negative, msgrcv returns the first message with mtype less // than or equal to the absolute value. @@ -298,8 +299,7 @@ TEST(MsgqueueTest, MsgRcvTypeNegative) { TEST(MsgqueueTest, MsgOpPermissions) { AutoCapability cap(CAP_IPC_OWNER, false); - Queue queue(msgget(IPC_PRIVATE, 0000)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0000)); msgbuf buf{1, ""}; @@ -311,8 +311,7 @@ TEST(MsgqueueTest, MsgOpPermissions) { // Test limits for messages and queues. TEST(MsgqueueTest, MsgOpLimits) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, "A message."}; @@ -340,11 +339,14 @@ bool MsgCopySupported() { // test if errno == ENOSYS. This means that the test will always run on // gVisor, but may be skipped on native linux. - Queue queue(msgget(IPC_PRIVATE, 0600)); - + auto maybe_id = Msgget(IPC_PRIVATE, 0600); + if (!maybe_id.ok()) { + return false; + } + Queue queue(std::move(maybe_id.ValueOrDie())); msgbuf buf{1, "Test message."}; - msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0); + msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0); return !(msgrcv(queue.get(), &buf, sizeof(buf.mtext) + 1, 0, MSG_COPY | IPC_NOWAIT) == -1 && errno == ENOSYS); @@ -354,9 +356,7 @@ bool MsgCopySupported() { TEST(MsgqueueTest, MsgCopy) { SKIP_IF(!MsgCopySupported()); - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); - + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf bufs[5] = { msgbuf{1, "Message 1."}, msgbuf{2, "Message 2."}, msgbuf{3, "Message 3."}, msgbuf{4, "Message 4."}, msgbuf{5, "Message 5."}, @@ -390,9 +390,7 @@ TEST(MsgqueueTest, MsgCopy) { TEST(MsgqueueTest, MsgCopyInvalidArgs) { SKIP_IF(!MsgCopySupported()); - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); - + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf rcv; EXPECT_THAT(msgrcv(queue.get(), &rcv, msgSize, 1, MSG_COPY), SyscallFailsWithErrno(EINVAL)); @@ -406,9 +404,7 @@ TEST(MsgqueueTest, MsgCopyInvalidArgs) { TEST(MsgqueueTest, MsgCopyInvalidIndex) { SKIP_IF(!MsgCopySupported()); - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); - + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf rcv; EXPECT_THAT(msgrcv(queue.get(), &rcv, msgSize, -3, MSG_COPY | IPC_NOWAIT), SyscallFailsWithErrno(ENOMSG)); @@ -419,9 +415,7 @@ TEST(MsgqueueTest, MsgCopyInvalidIndex) { // Test msgrcv (most probably) blocking on an empty queue. TEST(MsgqueueTest, MsgRcvBlocking) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); - + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf buf{1, "A message."}; ScopedThread t([&] { @@ -441,9 +435,7 @@ TEST(MsgqueueTest, MsgRcvBlocking) { // Test msgrcv (most probably) waiting for a specific-type message. TEST(MsgqueueTest, MsgRcvTypeBlocking) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); - + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgbuf bufs[5] = {{1, "A message."}, {1, "A message."}, {1, "A message."}, @@ -471,9 +463,7 @@ TEST(MsgqueueTest, MsgRcvTypeBlocking) { // Test msgsnd (most probably) blocking on a full queue. TEST(MsgqueueTest, MsgSndBlocking) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); - + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgmax buf{1, ""}; // Has max amount of bytes. const size_t msgCount = msgMnb / msgMax; // Number of messages that can be @@ -512,8 +502,7 @@ TEST(MsgqueueTest, MsgSndBlocking) { // Test removing a queue while a blocking msgsnd is executing. TEST(MsgqueueTest, MsgSndRmWhileBlocking) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); // Number of messages that can be sent without blocking. const size_t msgCount = msgMnb / msgMax; @@ -549,8 +538,7 @@ TEST(MsgqueueTest, MsgSndRmWhileBlocking) { // Test removing a queue while a blocking msgrcv is executing. TEST(MsgqueueTest, MsgRcvRmWhileBlocking) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); ScopedThread t([&] { // Because we're repeating on EINTR, msgsnd may race with msgctl(IPC_RMID) @@ -568,8 +556,7 @@ TEST(MsgqueueTest, MsgRcvRmWhileBlocking) { // Test a collection of msgsnd/msgrcv operations in different processes. TEST(MsgqueueTest, MsgOpGeneral) { - Queue queue(msgget(IPC_PRIVATE, 0600)); - ASSERT_THAT(queue.get(), SyscallSucceeds()); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); // Create multiple sending/receiving threads that send messages back and // forth. There's a matching recv for each send, so by the end of the test, @@ -625,7 +612,7 @@ TEST(MsgqueueTest, MsgOpGeneral) { void empty_sighandler(int sig, siginfo_t* info, void* context) {} TEST(MsgqueueTest, InterruptRecv) { - Queue queue(msgget(IPC_PRIVATE, 0600)); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); char buf[64]; absl::Notification done, exit; @@ -663,7 +650,7 @@ TEST(MsgqueueTest, InterruptRecv) { } TEST(MsgqueueTest, InterruptSend) { - Queue queue(msgget(IPC_PRIVATE, 0600)); + Queue queue = ASSERT_NO_ERRNO_AND_VALUE(Msgget(IPC_PRIVATE, 0600)); msgmax buf{1, ""}; // Number of messages that can be sent without blocking. const size_t msgCount = msgMnb / msgMax; @@ -708,6 +695,178 @@ TEST(MsgqueueTest, InterruptSend) { t.Join(); } +// Test msgctl with IPC_STAT option. +TEST(MsgqueueTest, MsgCtlIpcStat) { + auto start = absl::Now(); + + Queue queue(msgget(IPC_PRIVATE, 0600)); + ASSERT_THAT(queue.get(), SyscallSucceeds()); + + const uid_t uid = getuid(); + const gid_t gid = getgid(); + const pid_t pid = getpid(); + + struct msqid_ds ds; + ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), SyscallSucceeds()); + + EXPECT_EQ(ds.msg_perm.__key, IPC_PRIVATE); + EXPECT_EQ(ds.msg_perm.uid, uid); + EXPECT_EQ(ds.msg_perm.gid, gid); + EXPECT_EQ(ds.msg_perm.cuid, uid); + EXPECT_EQ(ds.msg_perm.cgid, gid); + EXPECT_EQ(ds.msg_perm.mode, 0600); + + EXPECT_EQ(ds.msg_stime, 0); + EXPECT_EQ(ds.msg_rtime, 0); + EXPECT_GE(ds.msg_ctime, absl::ToTimeT(start)); + + EXPECT_EQ(ds.msg_cbytes, 0); + EXPECT_EQ(ds.msg_qnum, 0); + EXPECT_EQ(ds.msg_qbytes, msgMnb); + EXPECT_EQ(ds.msg_lspid, 0); + EXPECT_EQ(ds.msg_lrpid, 0); + + // The timestamps only have a resolution of seconds; slow down so we actually + // see the timestamps change. + absl::SleepFor(absl::Seconds(1)); + auto pre_send = absl::Now(); + + msgbuf buf{1, "A message."}; + ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0), + SyscallSucceeds()); + + ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), SyscallSucceeds()); + + EXPECT_GE(ds.msg_stime, absl::ToTimeT(pre_send)); + EXPECT_EQ(ds.msg_rtime, 0); + EXPECT_GE(ds.msg_ctime, absl::ToTimeT(start)); + + EXPECT_EQ(ds.msg_cbytes, msgSize); + EXPECT_EQ(ds.msg_qnum, 1); + EXPECT_EQ(ds.msg_qbytes, msgMnb); + EXPECT_EQ(ds.msg_lspid, pid); + EXPECT_EQ(ds.msg_lrpid, 0); + + absl::SleepFor(absl::Seconds(1)); + auto pre_receive = absl::Now(); + + ASSERT_THAT(msgrcv(queue.get(), &buf, sizeof(buf.mtext), 0, 0), + SyscallSucceedsWithValue(msgSize)); + + ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), SyscallSucceeds()); + + EXPECT_GE(ds.msg_stime, absl::ToTimeT(pre_send)); + EXPECT_GE(ds.msg_rtime, absl::ToTimeT(pre_receive)); + EXPECT_GE(ds.msg_ctime, absl::ToTimeT(start)); + + EXPECT_EQ(ds.msg_cbytes, 0); + EXPECT_EQ(ds.msg_qnum, 0); + EXPECT_EQ(ds.msg_qbytes, msgMnb); + EXPECT_EQ(ds.msg_lspid, pid); + EXPECT_EQ(ds.msg_lrpid, pid); +} + +// Test msgctl with IPC_STAT option on a write-only queue. +TEST(MsgqueueTest, MsgCtlIpcStatWriteOnly) { + // Drop CAP_IPC_OWNER which allows us to bypass permissions. + AutoCapability cap(CAP_IPC_OWNER, false); + + Queue queue(msgget(IPC_PRIVATE, 0200)); + ASSERT_THAT(queue.get(), SyscallSucceeds()); + + struct msqid_ds ds; + ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), + SyscallFailsWithErrno(EACCES)); +} + +// Test msgctl with IPC_SET option. +TEST(MsgqueueTest, MsgCtlIpcSet) { + Queue queue(msgget(IPC_PRIVATE, 0600)); + ASSERT_THAT(queue.get(), SyscallSucceeds()); + + struct msqid_ds ds; + ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), SyscallSucceeds()); + EXPECT_EQ(ds.msg_perm.mode, 0600); + + ds.msg_perm.mode = 0777; + ASSERT_THAT(msgctl(queue.get(), IPC_SET, &ds), SyscallSucceeds()); + + ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), SyscallSucceeds()); + EXPECT_EQ(ds.msg_perm.mode, 0777); +} + +// Test increasing msg_qbytes beyond limit with IPC_SET. +TEST(MsgqueueTest, MsgCtlIpcSetMaxBytes) { + // Drop CAP_SYS_RESOURCE which allows us to increase msg_qbytes beyond the + // system parameter MSGMNB. + AutoCapability cap(CAP_SYS_RESOURCE, false); + + Queue queue(msgget(IPC_PRIVATE, 0600)); + ASSERT_THAT(queue.get(), SyscallSucceeds()); + + struct msqid_ds ds; + ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), SyscallSucceeds()); + EXPECT_EQ(ds.msg_qbytes, msgMnb); + + ds.msg_qbytes = msgMnb - 10; + ASSERT_THAT(msgctl(queue.get(), IPC_SET, &ds), SyscallSucceeds()); + + ASSERT_THAT(msgctl(queue.get(), IPC_STAT, &ds), SyscallSucceeds()); + EXPECT_EQ(ds.msg_qbytes, msgMnb - 10); + + ds.msg_qbytes = msgMnb + 10; + EXPECT_THAT(msgctl(queue.get(), IPC_SET, &ds), SyscallFailsWithErrno(EPERM)); +} + +// Test msgctl with IPC_INFO option. +TEST(MsgqueueTest, MsgCtlIpcInfo) { + struct msginfo info; + ASSERT_THAT(msgctl(0, IPC_INFO, reinterpret_cast<struct msqid_ds*>(&info)), + SyscallSucceeds()); + + EXPECT_GT(info.msgmax, 0); + EXPECT_GT(info.msgmni, 0); + EXPECT_GT(info.msgmnb, 0); + EXPECT_EQ(info.msgpool, msgPool); + EXPECT_EQ(info.msgmap, msgMap); + EXPECT_EQ(info.msgssz, msgSsz); + EXPECT_EQ(info.msgtql, msgTql); +} + +// Test msgctl with MSG_INFO option. +TEST(MsgqueueTest, MsgCtlMsgInfo) { + struct msginfo info; + ASSERT_THAT(msgctl(0, MSG_INFO, reinterpret_cast<struct msqid_ds*>(&info)), + SyscallSucceeds()); + + EXPECT_GT(info.msgmax, 0); + EXPECT_GT(info.msgmni, 0); + EXPECT_GT(info.msgmnb, 0); + EXPECT_EQ(info.msgpool, 0); // Number of queues in the system. + EXPECT_EQ(info.msgmap, 0); // Total number of messages in all queues. + EXPECT_EQ(info.msgtql, 0); // Total number of bytes in all messages. + EXPECT_EQ(info.msgssz, msgSsz); + + // Add a queue and a message. + Queue queue(msgget(IPC_PRIVATE, 0600)); + ASSERT_THAT(queue.get(), SyscallSucceeds()); + + msgbuf buf{1, "A message."}; + ASSERT_THAT(msgsnd(queue.get(), &buf, sizeof(buf.mtext), 0), + SyscallSucceeds()); + + ASSERT_THAT(msgctl(0, MSG_INFO, reinterpret_cast<struct msqid_ds*>(&info)), + SyscallSucceeds()); + + EXPECT_GT(info.msgmax, 0); + EXPECT_GT(info.msgmni, 0); + EXPECT_GT(info.msgmnb, 0); + EXPECT_EQ(info.msgpool, 1); // Number of queues in the system. + EXPECT_EQ(info.msgmap, 1); // Total number of messages in all queues. + EXPECT_EQ(info.msgtql, msgSize); // Total number of bytes in all messages. + EXPECT_EQ(info.msgssz, msgSsz); +} + } // namespace } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/network_namespace.cc b/test/syscalls/linux/network_namespace.cc index 1984feedd..aa9e1bc04 100644 --- a/test/syscalls/linux/network_namespace.cc +++ b/test/syscalls/linux/network_namespace.cc @@ -12,18 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <net/if.h> -#include <sched.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/types.h> - -#include "gmock/gmock.h" #include "gtest/gtest.h" +#include "test/syscalls/linux/ip_socket_test_util.h" #include "test/util/capability_util.h" -#include "test/util/posix_error.h" -#include "test/util/socket_util.h" -#include "test/util/test_util.h" #include "test/util/thread_util.h" namespace gvisor { @@ -37,13 +28,7 @@ TEST(NetworkNamespaceTest, LoopbackExists) { ASSERT_THAT(unshare(CLONE_NEWNET), SyscallSucceedsWithValue(0)); // TODO(gvisor.dev/issue/1833): Update this to test that only "lo" exists. - // Check loopback device exists. - int sock = socket(AF_INET, SOCK_DGRAM, 0); - ASSERT_THAT(sock, SyscallSucceeds()); - struct ifreq ifr; - strncpy(ifr.ifr_name, "lo", IFNAMSIZ); - EXPECT_THAT(ioctl(sock, SIOCGIFINDEX, &ifr), SyscallSucceeds()) - << "lo cannot be found"; + ASSERT_NE(ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()), 0); }); } diff --git a/test/syscalls/linux/packet_socket.cc b/test/syscalls/linux/packet_socket.cc index 98339277b..c706848fc 100644 --- a/test/syscalls/linux/packet_socket.cc +++ b/test/syscalls/linux/packet_socket.cc @@ -1,4 +1,4 @@ -// Copyright 2019 The gVisor Authors. +// 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. @@ -12,48 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <arpa/inet.h> -#include <ifaddrs.h> -#include <linux/capability.h> -#include <linux/if_arp.h> -#include <linux/if_packet.h> -#include <net/ethernet.h> -#include <netinet/in.h> -#include <netinet/ip.h> -#include <netinet/udp.h> +#include <net/if.h> +#include <netinet/if_ether.h> +#include <netpacket/packet.h> #include <poll.h> -#include <sys/ioctl.h> #include <sys/socket.h> #include <sys/types.h> -#include <unistd.h> + +#include <limits> #include "gtest/gtest.h" -#include "absl/base/internal/endian.h" -#include "test/syscalls/linux/unix_domain_socket_test_util.h" +#include "test/syscalls/linux/ip_socket_test_util.h" #include "test/util/capability_util.h" #include "test/util/file_descriptor.h" #include "test/util/socket_util.h" -#include "test/util/test_util.h" - -// Some of these tests involve sending packets via AF_PACKET sockets and the -// loopback interface. Because AF_PACKET circumvents so much of the networking -// stack, Linux sees these packets as "martian", i.e. they claim to be to/from -// localhost but don't have the usual associated data. Thus Linux drops them by -// default. You can see where this happens by following the code at: -// -// - net/ipv4/ip_input.c:ip_rcv_finish, which calls -// - net/ipv4/route.c:ip_route_input_noref, which calls -// - net/ipv4/route.c:ip_route_input_slow, which finds and drops martian -// packets. -// -// To tell Linux not to drop these packets, you need to tell it to accept our -// funny packets (which are completely valid and correct, but lack associated -// in-kernel data because we use AF_PACKET): -// -// echo 1 >> /proc/sys/net/ipv4/conf/lo/accept_local -// echo 1 >> /proc/sys/net/ipv4/conf/lo/route_localnet -// -// These tests require CAP_NET_RAW to run. namespace gvisor { namespace testing { @@ -61,496 +33,257 @@ namespace testing { namespace { using ::testing::AnyOf; +using ::testing::Combine; using ::testing::Eq; +using ::testing::Values; -constexpr char kMessage[] = "soweoneul malhaebwa"; -constexpr in_port_t kPort = 0x409c; // htons(40000) - -// -// "Cooked" tests. Cooked AF_PACKET sockets do not contain link layer -// headers, and provide link layer destination/source information via a -// returned struct sockaddr_ll. -// - -// Send kMessage via sock to loopback -void SendUDPMessage(int sock) { - struct sockaddr_in dest = {}; - dest.sin_port = kPort; - dest.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - dest.sin_family = AF_INET; - EXPECT_THAT(sendto(sock, kMessage, sizeof(kMessage), 0, - reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), - SyscallSucceedsWithValue(sizeof(kMessage))); -} - -// Send an IP packet and make sure ETH_P_<something else> doesn't pick it up. -TEST(BasicCookedPacketTest, WrongType) { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { - ASSERT_THAT(socket(AF_PACKET, SOCK_DGRAM, ETH_P_PUP), - SyscallFailsWithErrno(EPERM)); - GTEST_SKIP(); - } - - FileDescriptor sock = ASSERT_NO_ERRNO_AND_VALUE( - Socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_PUP))); - - // Let's use a simple IP payload: a UDP datagram. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - SendUDPMessage(udp_sock.get()); - - // Wait and make sure the socket never becomes readable. - struct pollfd pfd = {}; - pfd.fd = sock.get(); - pfd.events = POLLIN; - EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(0)); -} - -// Tests for "cooked" (SOCK_DGRAM) packet(7) sockets. -class CookedPacketTest : public ::testing::TestWithParam<int> { +class PacketSocketCreationTest + : public ::testing::TestWithParam<std::tuple<int, int>> { protected: - // Creates a socket to be used in tests. - void SetUp() override; - - // Closes the socket created by SetUp(). - void TearDown() override; - - // Gets the device index of the loopback device. - int GetLoopbackIndex(); - - // The socket used for both reading and writing. - int socket_; -}; - -void CookedPacketTest::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { - ASSERT_THAT(socket(AF_PACKET, SOCK_DGRAM, htons(GetParam())), - SyscallFailsWithErrno(EPERM)); - GTEST_SKIP(); - } - - if (!IsRunningOnGvisor()) { - FileDescriptor acceptLocal = ASSERT_NO_ERRNO_AND_VALUE( - Open("/proc/sys/net/ipv4/conf/lo/accept_local", O_RDONLY)); - FileDescriptor routeLocalnet = ASSERT_NO_ERRNO_AND_VALUE( - Open("/proc/sys/net/ipv4/conf/lo/route_localnet", O_RDONLY)); - char enabled; - ASSERT_THAT(read(acceptLocal.get(), &enabled, 1), - SyscallSucceedsWithValue(1)); - ASSERT_EQ(enabled, '1'); - ASSERT_THAT(read(routeLocalnet.get(), &enabled, 1), - SyscallSucceedsWithValue(1)); - ASSERT_EQ(enabled, '1'); + void SetUp() override { + if (!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())) { + const auto [type, protocol] = GetParam(); + ASSERT_THAT(socket(AF_PACKET, type, htons(protocol)), + SyscallFailsWithErrno(EPERM)); + GTEST_SKIP() << "Missing packet socket capability"; + } } +}; - ASSERT_THAT(socket_ = socket(AF_PACKET, SOCK_DGRAM, htons(GetParam())), - SyscallSucceeds()); +TEST_P(PacketSocketCreationTest, Create) { + const auto [type, protocol] = GetParam(); + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_PACKET, type, htons(protocol))); + EXPECT_GE(fd.get(), 0); } -void CookedPacketTest::TearDown() { - // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { - EXPECT_THAT(close(socket_), SyscallSucceeds()); - } -} +INSTANTIATE_TEST_SUITE_P(AllPacketSocketTests, PacketSocketCreationTest, + Combine(Values(SOCK_DGRAM, SOCK_RAW), + Values(0, 1, 255, ETH_P_IP, ETH_P_IPV6, + std::numeric_limits<uint16_t>::max()))); -int CookedPacketTest::GetLoopbackIndex() { - struct ifreq ifr; - snprintf(ifr.ifr_name, IFNAMSIZ, "lo"); - EXPECT_THAT(ioctl(socket_, SIOCGIFINDEX, &ifr), SyscallSucceeds()); - EXPECT_NE(ifr.ifr_ifindex, 0); - return ifr.ifr_ifindex; -} +class PacketSocketTest : public ::testing::TestWithParam<int> { + protected: + void SetUp() override { + if (!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())) { + ASSERT_THAT(socket(AF_PACKET, GetParam(), 0), + SyscallFailsWithErrno(EPERM)); + GTEST_SKIP() << "Missing packet socket capability"; + } -// Receive and verify the message via packet socket on interface. -void ReceiveMessage(int sock, int ifindex) { - // Wait for the socket to become readable. - struct pollfd pfd = {}; - pfd.fd = sock; - pfd.events = POLLIN; - EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 2000), SyscallSucceedsWithValue(1)); - - // Read and verify the data. - constexpr size_t packet_size = - sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kMessage); - char buf[64]; - struct sockaddr_ll src = {}; - socklen_t src_len = sizeof(src); - ASSERT_THAT(recvfrom(sock, buf, sizeof(buf), 0, - reinterpret_cast<struct sockaddr*>(&src), &src_len), - SyscallSucceedsWithValue(packet_size)); - - // sockaddr_ll ends with an 8 byte physical address field, but ethernet - // addresses only use 6 bytes. Linux used to return sizeof(sockaddr_ll)-2 - // here, but since commit b2cf86e1563e33a14a1c69b3e508d15dc12f804c returns - // sizeof(sockaddr_ll). - ASSERT_THAT(src_len, AnyOf(Eq(sizeof(src)), Eq(sizeof(src) - 2))); - - // Verify the source address. - EXPECT_EQ(src.sll_family, AF_PACKET); - EXPECT_EQ(src.sll_ifindex, ifindex); - EXPECT_EQ(src.sll_halen, ETH_ALEN); - EXPECT_EQ(ntohs(src.sll_protocol), ETH_P_IP); - // This came from the loopback device, so the address is all 0s. - for (int i = 0; i < src.sll_halen; i++) { - EXPECT_EQ(src.sll_addr[i], 0); + socket_ = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_PACKET, GetParam(), 0)); } - // Verify the IP header. We memcpy to deal with pointer aligment. - struct iphdr ip = {}; - memcpy(&ip, buf, sizeof(ip)); - EXPECT_EQ(ip.ihl, 5); - EXPECT_EQ(ip.version, 4); - EXPECT_EQ(ip.tot_len, htons(packet_size)); - EXPECT_EQ(ip.protocol, IPPROTO_UDP); - EXPECT_EQ(ip.daddr, htonl(INADDR_LOOPBACK)); - EXPECT_EQ(ip.saddr, htonl(INADDR_LOOPBACK)); - - // Verify the UDP header. We memcpy to deal with pointer aligment. - struct udphdr udp = {}; - memcpy(&udp, buf + sizeof(iphdr), sizeof(udp)); - EXPECT_EQ(udp.dest, kPort); - EXPECT_EQ(udp.len, htons(sizeof(udphdr) + sizeof(kMessage))); - - // Verify the payload. - char* payload = reinterpret_cast<char*>(buf + sizeof(iphdr) + sizeof(udphdr)); - EXPECT_EQ(strncmp(payload, kMessage, sizeof(kMessage)), 0); -} - -// Receive via a packet socket. -TEST_P(CookedPacketTest, Receive) { - // Let's use a simple IP payload: a UDP datagram. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - SendUDPMessage(udp_sock.get()); - - // Receive and verify the data. - int loopback_index = GetLoopbackIndex(); - ReceiveMessage(socket_, loopback_index); -} + FileDescriptor socket_; +}; -// Send via a packet socket. -TEST_P(CookedPacketTest, Send) { - // Let's send a UDP packet and receive it using a regular UDP socket. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - struct sockaddr_in bind_addr = {}; - bind_addr.sin_family = AF_INET; - bind_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - bind_addr.sin_port = kPort; - ASSERT_THAT( - bind(udp_sock.get(), reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr)), - SyscallSucceeds()); - - // Set up the destination physical address. - struct sockaddr_ll dest = {}; - dest.sll_family = AF_PACKET; - dest.sll_halen = ETH_ALEN; - dest.sll_ifindex = GetLoopbackIndex(); - dest.sll_protocol = htons(ETH_P_IP); - // We're sending to the loopback device, so the address is all 0s. - memset(dest.sll_addr, 0x00, ETH_ALEN); - - // Set up the IP header. - struct iphdr iphdr = {0}; - iphdr.ihl = 5; - iphdr.version = 4; - iphdr.tos = 0; - iphdr.tot_len = - htons(sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kMessage)); - // Get a pseudo-random ID. If we clash with an in-use ID the test will fail, - // but we have no way of getting an ID we know to be good. - srand(*reinterpret_cast<unsigned int*>(&iphdr)); - iphdr.id = rand(); - // Linux sets this bit ("do not fragment") for small packets. - iphdr.frag_off = 1 << 6; - iphdr.ttl = 64; - iphdr.protocol = IPPROTO_UDP; - iphdr.daddr = htonl(INADDR_LOOPBACK); - iphdr.saddr = htonl(INADDR_LOOPBACK); - iphdr.check = IPChecksum(iphdr); - - // Set up the UDP header. - struct udphdr udphdr = {}; - udphdr.source = kPort; - udphdr.dest = kPort; - udphdr.len = htons(sizeof(udphdr) + sizeof(kMessage)); - udphdr.check = UDPChecksum(iphdr, udphdr, kMessage, sizeof(kMessage)); - - // Copy both headers and the payload into our packet buffer. - char send_buf[sizeof(iphdr) + sizeof(udphdr) + sizeof(kMessage)]; - memcpy(send_buf, &iphdr, sizeof(iphdr)); - memcpy(send_buf + sizeof(iphdr), &udphdr, sizeof(udphdr)); - memcpy(send_buf + sizeof(iphdr) + sizeof(udphdr), kMessage, sizeof(kMessage)); - - // We don't implement writing to packet sockets on gVisor. - if (IsRunningOnGvisor()) { - ASSERT_THAT(sendto(socket_, send_buf, sizeof(send_buf), 0, - reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), - SyscallFailsWithErrno(EINVAL)); - GTEST_SKIP(); +TEST_P(PacketSocketTest, GetSockName) { + { + // First check the local address of an unbound packet socket. + sockaddr_ll addr; + socklen_t addrlen = sizeof(addr); + ASSERT_THAT(getsockname(socket_.get(), reinterpret_cast<sockaddr*>(&addr), + &addrlen), + SyscallSucceeds()); + // sockaddr_ll ends with an 8 byte physical address field, but only the + // bytes that are used in the sockaddr_ll.sll_addr field are included in the + // address length. Seems Linux used to return the size of sockaddr_ll, but + // https://github.com/torvalds/linux/commit/0fb375fb9b93b7d822debc6a734052337ccfdb1f + // changed things to only return `sizeof(sockaddr_ll) + sll.sll_addr`. + ASSERT_THAT(addrlen, AnyOf(Eq(sizeof(addr)), + Eq(sizeof(addr) - sizeof(addr.sll_addr)))); + EXPECT_EQ(addr.sll_family, AF_PACKET); + EXPECT_EQ(addr.sll_ifindex, 0); + if (IsRunningOnGvisor() && !IsRunningWithHostinet()) { + // TODO(https://gvisor.dev/issue/6530): Do not assume all interfaces have + // an ethernet address. + EXPECT_EQ(addr.sll_halen, ETH_ALEN); + } else { + EXPECT_EQ(addr.sll_halen, 0); + } + EXPECT_EQ(ntohs(addr.sll_protocol), 0); + EXPECT_EQ(addr.sll_hatype, 0); } - - // Send it. - ASSERT_THAT(sendto(socket_, send_buf, sizeof(send_buf), 0, - reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), - SyscallSucceedsWithValue(sizeof(send_buf))); - - // Wait for the packet to become available on both sockets. - struct pollfd pfd = {}; - pfd.fd = udp_sock.get(); - pfd.events = POLLIN; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 5000), SyscallSucceedsWithValue(1)); - pfd.fd = socket_; - pfd.events = POLLIN; - ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 5000), SyscallSucceedsWithValue(1)); - - // Receive on the packet socket. - char recv_buf[sizeof(send_buf)]; - ASSERT_THAT(recv(socket_, recv_buf, sizeof(recv_buf), 0), - SyscallSucceedsWithValue(sizeof(recv_buf))); - ASSERT_EQ(memcmp(recv_buf, send_buf, sizeof(send_buf)), 0); - - // Receive on the UDP socket. - struct sockaddr_in src; - socklen_t src_len = sizeof(src); - ASSERT_THAT(recvfrom(udp_sock.get(), recv_buf, sizeof(recv_buf), MSG_DONTWAIT, - reinterpret_cast<struct sockaddr*>(&src), &src_len), - SyscallSucceedsWithValue(sizeof(kMessage))); - // Check src and payload. - EXPECT_EQ(strncmp(recv_buf, kMessage, sizeof(kMessage)), 0); - EXPECT_EQ(src.sin_family, AF_INET); - EXPECT_EQ(src.sin_port, kPort); - EXPECT_EQ(src.sin_addr.s_addr, htonl(INADDR_LOOPBACK)); -} - -// Bind and receive via packet socket. -TEST_P(CookedPacketTest, BindReceive) { - struct sockaddr_ll bind_addr = {}; - bind_addr.sll_family = AF_PACKET; - bind_addr.sll_protocol = htons(GetParam()); - bind_addr.sll_ifindex = GetLoopbackIndex(); - - ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr), + // Next we bind the socket to loopback before checking the local address. + const sockaddr_ll bind_addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IP), + .sll_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()), + }; + ASSERT_THAT(bind(socket_.get(), reinterpret_cast<const sockaddr*>(&bind_addr), sizeof(bind_addr)), SyscallSucceeds()); - - // Let's use a simple IP payload: a UDP datagram. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - SendUDPMessage(udp_sock.get()); - - // Receive and verify the data. - ReceiveMessage(socket_, bind_addr.sll_ifindex); -} - -// Double Bind socket. -TEST_P(CookedPacketTest, DoubleBindSucceeds) { - struct sockaddr_ll bind_addr = {}; - bind_addr.sll_family = AF_PACKET; - bind_addr.sll_protocol = htons(GetParam()); - bind_addr.sll_ifindex = GetLoopbackIndex(); - - ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr)), - SyscallSucceeds()); - - // Binding socket again should fail. - ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr)), - // Linux 4.09 returns EINVAL here, but some time before 4.19 it - // switched to EADDRINUSE. - SyscallSucceeds()); -} - -// Bind and verify we do not receive data on interface which is not bound -TEST_P(CookedPacketTest, BindDrop) { - // Let's use a simple IP payload: a UDP datagram. - FileDescriptor udp_sock = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - - struct ifaddrs* if_addr_list = nullptr; - auto cleanup = Cleanup([&if_addr_list]() { freeifaddrs(if_addr_list); }); - - ASSERT_THAT(getifaddrs(&if_addr_list), SyscallSucceeds()); - - // Get interface other than loopback. - struct ifreq ifr = {}; - for (struct ifaddrs* i = if_addr_list; i; i = i->ifa_next) { - if (strcmp(i->ifa_name, "lo") != 0) { - strncpy(ifr.ifr_name, i->ifa_name, sizeof(ifr.ifr_name)); - break; + { + sockaddr_ll addr; + socklen_t addrlen = sizeof(addr); + ASSERT_THAT(getsockname(socket_.get(), reinterpret_cast<sockaddr*>(&addr), + &addrlen), + SyscallSucceeds()); + ASSERT_THAT(addrlen, + AnyOf(Eq(sizeof(addr)), + Eq(sizeof(addr) - sizeof(addr.sll_addr) + ETH_ALEN))); + EXPECT_EQ(addr.sll_family, AF_PACKET); + EXPECT_EQ(addr.sll_ifindex, bind_addr.sll_ifindex); + EXPECT_EQ(addr.sll_halen, ETH_ALEN); + // Bound to loopback which has the all zeroes address. + for (int i = 0; i < addr.sll_halen; ++i) { + EXPECT_EQ(addr.sll_addr[i], 0) << "byte mismatch @ idx = " << i; + } + EXPECT_EQ(ntohs(addr.sll_protocol), htons(addr.sll_protocol)); + if (IsRunningOnGvisor() && !IsRunningWithHostinet()) { + // TODO(https://gvisor.dev/issue/6621): Support populating sll_hatype. + EXPECT_EQ(addr.sll_hatype, 0); + } else { + EXPECT_EQ(addr.sll_hatype, ARPHRD_LOOPBACK); } } - - // Skip if no interface is available other than loopback. - if (strlen(ifr.ifr_name) == 0) { - GTEST_SKIP(); - } - - // Get interface index. - EXPECT_THAT(ioctl(socket_, SIOCGIFINDEX, &ifr), SyscallSucceeds()); - EXPECT_NE(ifr.ifr_ifindex, 0); - - // Bind to packet socket requires only family, protocol and ifindex. - struct sockaddr_ll bind_addr = {}; - bind_addr.sll_family = AF_PACKET; - bind_addr.sll_protocol = htons(GetParam()); - bind_addr.sll_ifindex = ifr.ifr_ifindex; - - ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr), - sizeof(bind_addr)), - SyscallSucceeds()); - - // Send to loopback interface. - struct sockaddr_in dest = {}; - dest.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - dest.sin_family = AF_INET; - dest.sin_port = kPort; - EXPECT_THAT(sendto(udp_sock.get(), kMessage, sizeof(kMessage), 0, - reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), - SyscallSucceedsWithValue(sizeof(kMessage))); - - // Wait and make sure the socket never receives any data. - struct pollfd pfd = {}; - pfd.fd = socket_; - pfd.events = POLLIN; - EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(0)); } -// Verify that we receive outbound packets. This test requires at least one -// non loopback interface so that we can actually capture an outgoing packet. -TEST_P(CookedPacketTest, ReceiveOutbound) { - // Only ETH_P_ALL sockets can receive outbound packets on linux. - SKIP_IF(GetParam() != ETH_P_ALL); +TEST_P(PacketSocketTest, RebindProtocol) { + const bool kEthHdrIncluded = GetParam() == SOCK_RAW; + + sockaddr_in udp_bind_addr = { + .sin_family = AF_INET, + .sin_addr = {.s_addr = htonl(INADDR_LOOPBACK)}, + }; - // Let's use a simple IP payload: a UDP datagram. FileDescriptor udp_sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); - - struct ifaddrs* if_addr_list = nullptr; - auto cleanup = Cleanup([&if_addr_list]() { freeifaddrs(if_addr_list); }); - - ASSERT_THAT(getifaddrs(&if_addr_list), SyscallSucceeds()); - - // Get interface other than loopback. - struct ifreq ifr = {}; - for (struct ifaddrs* i = if_addr_list; i; i = i->ifa_next) { - if (strcmp(i->ifa_name, "lo") != 0) { - strncpy(ifr.ifr_name, i->ifa_name, sizeof(ifr.ifr_name)); - break; - } - } - - // Skip if no interface is available other than loopback. - if (strlen(ifr.ifr_name) == 0) { - GTEST_SKIP(); + { + // Bind the socket so that we have something to send packets to. + // + // If we didn't do this, the UDP packets we send will be responded to with + // ICMP Destination Port Unreachable errors. + ASSERT_THAT( + bind(udp_sock.get(), reinterpret_cast<const sockaddr*>(&udp_bind_addr), + sizeof(udp_bind_addr)), + SyscallSucceeds()); + socklen_t addrlen = sizeof(udp_bind_addr); + ASSERT_THAT( + getsockname(udp_sock.get(), reinterpret_cast<sockaddr*>(&udp_bind_addr), + &addrlen), + SyscallSucceeds()); + ASSERT_THAT(addrlen, sizeof(udp_bind_addr)); } - // Get interface index and name. - EXPECT_THAT(ioctl(socket_, SIOCGIFINDEX, &ifr), SyscallSucceeds()); - EXPECT_NE(ifr.ifr_ifindex, 0); - int ifindex = ifr.ifr_ifindex; - - constexpr int kMACSize = 6; - char hwaddr[kMACSize]; - // Get interface address. - ASSERT_THAT(ioctl(socket_, SIOCGIFHWADDR, &ifr), SyscallSucceeds()); - ASSERT_THAT(ifr.ifr_hwaddr.sa_family, - AnyOf(Eq(ARPHRD_NONE), Eq(ARPHRD_ETHER))); - memcpy(hwaddr, ifr.ifr_hwaddr.sa_data, kMACSize); - - // Just send it to the google dns server 8.8.8.8. It's UDP we don't care - // if it actually gets to the DNS Server we just want to see that we receive - // it on our AF_PACKET socket. - // - // NOTE: We just want to pick an IP that is non-local to avoid having to - // handle ARP as this should cause the UDP packet to be sent to the default - // gateway configured for the system under test. Otherwise the only packet we - // will see is the ARP query unless we picked an IP which will actually - // resolve. The test is a bit brittle but this was the best compromise for - // now. - struct sockaddr_in dest = {}; - ASSERT_EQ(inet_pton(AF_INET, "8.8.8.8", &dest.sin_addr.s_addr), 1); - dest.sin_family = AF_INET; - dest.sin_port = kPort; - EXPECT_THAT(sendto(udp_sock.get(), kMessage, sizeof(kMessage), 0, - reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), - SyscallSucceedsWithValue(sizeof(kMessage))); - - // Wait and make sure the socket receives the data. - struct pollfd pfd = {}; - pfd.fd = socket_; - pfd.events = POLLIN; - EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(1)); - - // Now read and check that the packet is the one we just sent. - // Read and verify the data. - constexpr size_t packet_size = - sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kMessage); - char buf[64]; - struct sockaddr_ll src = {}; - socklen_t src_len = sizeof(src); - ASSERT_THAT(recvfrom(socket_, buf, sizeof(buf), 0, - reinterpret_cast<struct sockaddr*>(&src), &src_len), - SyscallSucceedsWithValue(packet_size)); - - // sockaddr_ll ends with an 8 byte physical address field, but ethernet - // addresses only use 6 bytes. Linux used to return sizeof(sockaddr_ll)-2 - // here, but since commit b2cf86e1563e33a14a1c69b3e508d15dc12f804c returns - // sizeof(sockaddr_ll). - ASSERT_THAT(src_len, AnyOf(Eq(sizeof(src)), Eq(sizeof(src) - 2))); - - // Verify the source address. - EXPECT_EQ(src.sll_family, AF_PACKET); - EXPECT_EQ(src.sll_ifindex, ifindex); - EXPECT_EQ(src.sll_halen, ETH_ALEN); - EXPECT_EQ(ntohs(src.sll_protocol), ETH_P_IP); - EXPECT_EQ(src.sll_pkttype, PACKET_OUTGOING); - // Verify the link address of the interface matches that of the non - // non loopback interface address we stored above. - for (int i = 0; i < src.sll_halen; i++) { - EXPECT_EQ(src.sll_addr[i], hwaddr[i]); - } + const int loopback_index = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); + + auto send_udp_message = [&](const uint64_t v) { + ASSERT_THAT( + sendto(udp_sock.get(), reinterpret_cast<const char*>(&v), sizeof(v), + 0 /* flags */, reinterpret_cast<const sockaddr*>(&udp_bind_addr), + sizeof(udp_bind_addr)), + SyscallSucceeds()); + }; + + auto bind_to_network_protocol = [&](uint16_t protocol) { + const sockaddr_ll packet_bind_addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(protocol), + .sll_ifindex = loopback_index, + }; + + ASSERT_THAT(bind(socket_.get(), + reinterpret_cast<const sockaddr*>(&packet_bind_addr), + sizeof(packet_bind_addr)), + SyscallSucceeds()); + }; + + auto test_recv = [&, this](const uint64_t v) { + constexpr int kInfiniteTimeout = -1; + pollfd pfd = { + .fd = socket_.get(), + .events = POLLIN, + }; + ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kInfiniteTimeout), + SyscallSucceedsWithValue(1)); - // Verify the IP header. - struct iphdr ip = {}; - memcpy(&ip, buf, sizeof(ip)); - EXPECT_EQ(ip.ihl, 5); - EXPECT_EQ(ip.version, 4); - EXPECT_EQ(ip.tot_len, htons(packet_size)); - EXPECT_EQ(ip.protocol, IPPROTO_UDP); - EXPECT_EQ(ip.daddr, dest.sin_addr.s_addr); - EXPECT_NE(ip.saddr, htonl(INADDR_LOOPBACK)); - - // Verify the UDP header. - struct udphdr udp = {}; - memcpy(&udp, buf + sizeof(iphdr), sizeof(udp)); - EXPECT_EQ(udp.dest, kPort); - EXPECT_EQ(udp.len, htons(sizeof(udphdr) + sizeof(kMessage))); - - // Verify the payload. - char* payload = reinterpret_cast<char*>(buf + sizeof(iphdr) + sizeof(udphdr)); - EXPECT_EQ(strncmp(payload, kMessage, sizeof(kMessage)), 0); -} + struct { + ethhdr eth; + iphdr ip; + udphdr udp; + uint64_t payload; + char unused; + } ABSL_ATTRIBUTE_PACKED read_pkt; + sockaddr_ll src; + socklen_t src_len = sizeof(src); + + char* buf = reinterpret_cast<char*>(&read_pkt); + size_t buflen = sizeof(read_pkt); + size_t expected_read_len = sizeof(read_pkt) - sizeof(read_pkt.unused); + if (!kEthHdrIncluded) { + buf += sizeof(read_pkt.eth); + buflen -= sizeof(read_pkt.eth); + expected_read_len -= sizeof(read_pkt.eth); + } -// Bind with invalid address. -TEST_P(CookedPacketTest, BindFail) { - // Null address. - ASSERT_THAT( - bind(socket_, nullptr, sizeof(struct sockaddr)), - AnyOf(SyscallFailsWithErrno(EFAULT), SyscallFailsWithErrno(EINVAL))); - - // Address of size 1. - uint8_t addr = 0; - ASSERT_THAT( - bind(socket_, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), - SyscallFailsWithErrno(EINVAL)); + ASSERT_THAT(recvfrom(socket_.get(), buf, buflen, 0, + reinterpret_cast<sockaddr*>(&src), &src_len), + SyscallSucceedsWithValue(expected_read_len)); + // sockaddr_ll ends with an 8 byte physical address field, but ethernet + // addresses only use 6 bytes. Linux used to return sizeof(sockaddr_ll)-2 + // here, but returns sizeof(sockaddr_ll) since + // https://github.com/torvalds/linux/commit/b2cf86e1563e33a14a1c69b3e508d15dc12f804c. + ASSERT_THAT(src_len, + AnyOf(Eq(sizeof(src)), + Eq(sizeof(src) - sizeof(src.sll_addr) + ETH_ALEN))); + EXPECT_EQ(src.sll_family, AF_PACKET); + EXPECT_EQ(src.sll_ifindex, loopback_index); + EXPECT_EQ(src.sll_halen, ETH_ALEN); + EXPECT_EQ(ntohs(src.sll_protocol), ETH_P_IP); + // This came from the loopback device, so the address is all 0s. + constexpr uint8_t allZeroesMAC[ETH_ALEN] = {}; + EXPECT_EQ(memcmp(src.sll_addr, allZeroesMAC, sizeof(allZeroesMAC)), 0); + if (kEthHdrIncluded) { + EXPECT_EQ(memcmp(read_pkt.eth.h_dest, allZeroesMAC, sizeof(allZeroesMAC)), + 0); + EXPECT_EQ( + memcmp(read_pkt.eth.h_source, allZeroesMAC, sizeof(allZeroesMAC)), 0); + EXPECT_EQ(ntohs(read_pkt.eth.h_proto), ETH_P_IP); + } + // IHL hold the size of the header in 4 byte units. + EXPECT_EQ(read_pkt.ip.ihl, sizeof(iphdr) / 4); + EXPECT_EQ(read_pkt.ip.version, IPVERSION); + const uint16_t ip_pkt_size = + sizeof(read_pkt) - sizeof(read_pkt.eth) - sizeof(read_pkt.unused); + EXPECT_EQ(ntohs(read_pkt.ip.tot_len), ip_pkt_size); + EXPECT_EQ(read_pkt.ip.protocol, IPPROTO_UDP); + EXPECT_EQ(ntohl(read_pkt.ip.daddr), INADDR_LOOPBACK); + EXPECT_EQ(ntohl(read_pkt.ip.saddr), INADDR_LOOPBACK); + EXPECT_EQ(read_pkt.udp.source, udp_bind_addr.sin_port); + EXPECT_EQ(read_pkt.udp.dest, udp_bind_addr.sin_port); + EXPECT_EQ(ntohs(read_pkt.udp.len), ip_pkt_size - sizeof(read_pkt.ip)); + EXPECT_EQ(read_pkt.payload, v); + }; + + // The packet socket is not bound to IPv4 so we should not receive the sent + // message. + uint64_t counter = 0; + ASSERT_NO_FATAL_FAILURE(send_udp_message(++counter)); + + // Bind to IPv4 and expect to receive the UDP packet we send after binding. + ASSERT_NO_FATAL_FAILURE(bind_to_network_protocol(ETH_P_IP)); + ASSERT_NO_FATAL_FAILURE(send_udp_message(++counter)); + ASSERT_NO_FATAL_FAILURE(test_recv(counter)); + + // Bind the packet socket to a random protocol. + ASSERT_NO_FATAL_FAILURE(bind_to_network_protocol(255)); + ASSERT_NO_FATAL_FAILURE(send_udp_message(++counter)); + + // Bind back to IPv4 and expect to the UDP packet we send after binding + // back to IPv4. + ASSERT_NO_FATAL_FAILURE(bind_to_network_protocol(ETH_P_IP)); + ASSERT_NO_FATAL_FAILURE(send_udp_message(++counter)); + ASSERT_NO_FATAL_FAILURE(test_recv(counter)); + + // A zero valued protocol number should not change the bound network protocol. + ASSERT_NO_FATAL_FAILURE(bind_to_network_protocol(0)); + ASSERT_NO_FATAL_FAILURE(send_udp_message(++counter)); + ASSERT_NO_FATAL_FAILURE(test_recv(counter)); } -INSTANTIATE_TEST_SUITE_P(AllInetTests, CookedPacketTest, - ::testing::Values(ETH_P_IP, ETH_P_ALL)); +INSTANTIATE_TEST_SUITE_P(AllPacketSocketTests, PacketSocketTest, + Values(SOCK_DGRAM, SOCK_RAW)); } // namespace diff --git a/test/syscalls/linux/packet_socket_dgram.cc b/test/syscalls/linux/packet_socket_dgram.cc new file mode 100644 index 000000000..9515a48f2 --- /dev/null +++ b/test/syscalls/linux/packet_socket_dgram.cc @@ -0,0 +1,550 @@ +// Copyright 2019 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 <arpa/inet.h> +#include <ifaddrs.h> +#include <net/ethernet.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/udp.h> +#include <netpacket/packet.h> +#include <poll.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include "gtest/gtest.h" +#include "absl/base/internal/endian.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/cleanup.h" +#include "test/util/file_descriptor.h" +#include "test/util/socket_util.h" +#include "test/util/test_util.h" + +// Some of these tests involve sending packets via AF_PACKET sockets and the +// loopback interface. Because AF_PACKET circumvents so much of the networking +// stack, Linux sees these packets as "martian", i.e. they claim to be to/from +// localhost but don't have the usual associated data. Thus Linux drops them by +// default. You can see where this happens by following the code at: +// +// - net/ipv4/ip_input.c:ip_rcv_finish, which calls +// - net/ipv4/route.c:ip_route_input_noref, which calls +// - net/ipv4/route.c:ip_route_input_slow, which finds and drops martian +// packets. +// +// To tell Linux not to drop these packets, you need to tell it to accept our +// funny packets (which are completely valid and correct, but lack associated +// in-kernel data because we use AF_PACKET): +// +// echo 1 >> /proc/sys/net/ipv4/conf/lo/accept_local +// echo 1 >> /proc/sys/net/ipv4/conf/lo/route_localnet +// +// These tests require CAP_NET_RAW to run. + +namespace gvisor { +namespace testing { + +namespace { + +using ::testing::AnyOf; +using ::testing::Eq; + +constexpr char kMessage[] = "soweoneul malhaebwa"; +constexpr in_port_t kPort = 0x409c; // htons(40000) + +// +// "Cooked" tests. Cooked AF_PACKET sockets do not contain link layer +// headers, and provide link layer destination/source information via a +// returned struct sockaddr_ll. +// + +// Send kMessage via sock to loopback +void SendUDPMessage(int sock) { + struct sockaddr_in dest = {}; + dest.sin_port = kPort; + dest.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + dest.sin_family = AF_INET; + EXPECT_THAT(sendto(sock, kMessage, sizeof(kMessage), 0, + reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), + SyscallSucceedsWithValue(sizeof(kMessage))); +} + +// Send an IP packet and make sure ETH_P_<something else> doesn't pick it up. +TEST(BasicCookedPacketTest, WrongType) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())) { + ASSERT_THAT(socket(AF_PACKET, SOCK_DGRAM, ETH_P_PUP), + SyscallFailsWithErrno(EPERM)); + GTEST_SKIP(); + } + + FileDescriptor sock = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_PUP))); + + // Let's use a simple IP payload: a UDP datagram. + FileDescriptor udp_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); + SendUDPMessage(udp_sock.get()); + + // Wait and make sure the socket never becomes readable. + struct pollfd pfd = {}; + pfd.fd = sock.get(); + pfd.events = POLLIN; + EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(0)); +} + +// Tests for "cooked" (SOCK_DGRAM) packet(7) sockets. +class CookedPacketTest : public ::testing::TestWithParam<int> { + protected: + // Creates a socket to be used in tests. + void SetUp() override; + + // Closes the socket created by SetUp(). + void TearDown() override; + + // Gets the device index of the loopback device. + int GetLoopbackIndex(); + + // The socket used for both reading and writing. + int socket_; +}; + +void CookedPacketTest::SetUp() { + if (!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())) { + ASSERT_THAT(socket(AF_PACKET, SOCK_DGRAM, htons(GetParam())), + SyscallFailsWithErrno(EPERM)); + GTEST_SKIP(); + } + + if (!IsRunningOnGvisor()) { + FileDescriptor acceptLocal = ASSERT_NO_ERRNO_AND_VALUE( + Open("/proc/sys/net/ipv4/conf/lo/accept_local", O_RDONLY)); + FileDescriptor routeLocalnet = ASSERT_NO_ERRNO_AND_VALUE( + Open("/proc/sys/net/ipv4/conf/lo/route_localnet", O_RDONLY)); + char enabled; + ASSERT_THAT(read(acceptLocal.get(), &enabled, 1), + SyscallSucceedsWithValue(1)); + ASSERT_EQ(enabled, '1'); + ASSERT_THAT(read(routeLocalnet.get(), &enabled, 1), + SyscallSucceedsWithValue(1)); + ASSERT_EQ(enabled, '1'); + } + + ASSERT_THAT(socket_ = socket(AF_PACKET, SOCK_DGRAM, htons(GetParam())), + SyscallSucceeds()); +} + +void CookedPacketTest::TearDown() { + // TearDown will be run even if we skip the test. + if (ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())) { + EXPECT_THAT(close(socket_), SyscallSucceeds()); + } +} + +int CookedPacketTest::GetLoopbackIndex() { + int v = EXPECT_NO_ERRNO_AND_VALUE(gvisor::testing::GetLoopbackIndex()); + EXPECT_NE(v, 0); + return v; +} + +// Receive and verify the message via packet socket on interface. +void ReceiveMessage(int sock, int ifindex) { + // Wait for the socket to become readable. + struct pollfd pfd = {}; + pfd.fd = sock; + pfd.events = POLLIN; + EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 2000), SyscallSucceedsWithValue(1)); + + // Read and verify the data. + constexpr size_t packet_size = + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kMessage); + char buf[64]; + struct sockaddr_ll src = {}; + socklen_t src_len = sizeof(src); + ASSERT_THAT(recvfrom(sock, buf, sizeof(buf), 0, + reinterpret_cast<struct sockaddr*>(&src), &src_len), + SyscallSucceedsWithValue(packet_size)); + + // sockaddr_ll ends with an 8 byte physical address field, but ethernet + // addresses only use 6 bytes. Linux used to return sizeof(sockaddr_ll)-2 + // here, but since commit b2cf86e1563e33a14a1c69b3e508d15dc12f804c returns + // sizeof(sockaddr_ll). + ASSERT_THAT(src_len, AnyOf(Eq(sizeof(src)), Eq(sizeof(src) - 2))); + + // Verify the source address. + EXPECT_EQ(src.sll_family, AF_PACKET); + EXPECT_EQ(src.sll_ifindex, ifindex); + EXPECT_EQ(src.sll_halen, ETH_ALEN); + EXPECT_EQ(ntohs(src.sll_protocol), ETH_P_IP); + // This came from the loopback device, so the address is all 0s. + for (int i = 0; i < src.sll_halen; i++) { + EXPECT_EQ(src.sll_addr[i], 0); + } + + // Verify the IP header. We memcpy to deal with pointer aligment. + struct iphdr ip = {}; + memcpy(&ip, buf, sizeof(ip)); + EXPECT_EQ(ip.ihl, 5); + EXPECT_EQ(ip.version, 4); + EXPECT_EQ(ip.tot_len, htons(packet_size)); + EXPECT_EQ(ip.protocol, IPPROTO_UDP); + EXPECT_EQ(ip.daddr, htonl(INADDR_LOOPBACK)); + EXPECT_EQ(ip.saddr, htonl(INADDR_LOOPBACK)); + + // Verify the UDP header. We memcpy to deal with pointer aligment. + struct udphdr udp = {}; + memcpy(&udp, buf + sizeof(iphdr), sizeof(udp)); + EXPECT_EQ(udp.dest, kPort); + EXPECT_EQ(udp.len, htons(sizeof(udphdr) + sizeof(kMessage))); + + // Verify the payload. + char* payload = reinterpret_cast<char*>(buf + sizeof(iphdr) + sizeof(udphdr)); + EXPECT_EQ(strncmp(payload, kMessage, sizeof(kMessage)), 0); +} + +// Receive via a packet socket. +TEST_P(CookedPacketTest, Receive) { + // Let's use a simple IP payload: a UDP datagram. + FileDescriptor udp_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); + SendUDPMessage(udp_sock.get()); + + // Receive and verify the data. + int loopback_index = GetLoopbackIndex(); + ReceiveMessage(socket_, loopback_index); +} + +// Send via a packet socket. +TEST_P(CookedPacketTest, Send) { + // Let's send a UDP packet and receive it using a regular UDP socket. + FileDescriptor udp_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); + struct sockaddr_in bind_addr = {}; + bind_addr.sin_family = AF_INET; + bind_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + bind_addr.sin_port = kPort; + ASSERT_THAT( + bind(udp_sock.get(), reinterpret_cast<struct sockaddr*>(&bind_addr), + sizeof(bind_addr)), + SyscallSucceeds()); + + // Set up the destination physical address. + struct sockaddr_ll dest = {}; + dest.sll_family = AF_PACKET; + dest.sll_halen = ETH_ALEN; + dest.sll_ifindex = GetLoopbackIndex(); + dest.sll_protocol = htons(ETH_P_IP); + // We're sending to the loopback device, so the address is all 0s. + memset(dest.sll_addr, 0x00, ETH_ALEN); + + // Set up the IP header. + struct iphdr iphdr = {0}; + iphdr.ihl = 5; + iphdr.version = 4; + iphdr.tos = 0; + iphdr.tot_len = + htons(sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kMessage)); + // Get a pseudo-random ID. If we clash with an in-use ID the test will fail, + // but we have no way of getting an ID we know to be good. + srand(*reinterpret_cast<unsigned int*>(&iphdr)); + iphdr.id = rand(); + // Linux sets this bit ("do not fragment") for small packets. + iphdr.frag_off = 1 << 6; + iphdr.ttl = 64; + iphdr.protocol = IPPROTO_UDP; + iphdr.daddr = htonl(INADDR_LOOPBACK); + iphdr.saddr = htonl(INADDR_LOOPBACK); + iphdr.check = IPChecksum(iphdr); + + // Set up the UDP header. + struct udphdr udphdr = {}; + udphdr.source = kPort; + udphdr.dest = kPort; + udphdr.len = htons(sizeof(udphdr) + sizeof(kMessage)); + udphdr.check = UDPChecksum(iphdr, udphdr, kMessage, sizeof(kMessage)); + + // Copy both headers and the payload into our packet buffer. + char send_buf[sizeof(iphdr) + sizeof(udphdr) + sizeof(kMessage)]; + memcpy(send_buf, &iphdr, sizeof(iphdr)); + memcpy(send_buf + sizeof(iphdr), &udphdr, sizeof(udphdr)); + memcpy(send_buf + sizeof(iphdr) + sizeof(udphdr), kMessage, sizeof(kMessage)); + + // Send it. + ASSERT_THAT(sendto(socket_, send_buf, sizeof(send_buf), 0, + reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), + SyscallSucceedsWithValue(sizeof(send_buf))); + + // Wait for the packet to become available on both sockets. + struct pollfd pfd = {}; + pfd.fd = udp_sock.get(); + pfd.events = POLLIN; + ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 5000), SyscallSucceedsWithValue(1)); + pfd.fd = socket_; + pfd.events = POLLIN; + ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 5000), SyscallSucceedsWithValue(1)); + + // Receive on the packet socket. + char recv_buf[sizeof(send_buf)]; + ASSERT_THAT(recv(socket_, recv_buf, sizeof(recv_buf), 0), + SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_EQ(memcmp(recv_buf, send_buf, sizeof(send_buf)), 0); + + // Receive on the UDP socket. + struct sockaddr_in src; + socklen_t src_len = sizeof(src); + ASSERT_THAT(recvfrom(udp_sock.get(), recv_buf, sizeof(recv_buf), MSG_DONTWAIT, + reinterpret_cast<struct sockaddr*>(&src), &src_len), + SyscallSucceedsWithValue(sizeof(kMessage))); + // Check src and payload. + EXPECT_EQ(strncmp(recv_buf, kMessage, sizeof(kMessage)), 0); + EXPECT_EQ(src.sin_family, AF_INET); + EXPECT_EQ(src.sin_port, kPort); + EXPECT_EQ(src.sin_addr.s_addr, htonl(INADDR_LOOPBACK)); +} + +// Bind and receive via packet socket. +TEST_P(CookedPacketTest, BindReceive) { + struct sockaddr_ll bind_addr = {}; + bind_addr.sll_family = AF_PACKET; + bind_addr.sll_protocol = htons(GetParam()); + bind_addr.sll_ifindex = GetLoopbackIndex(); + + ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr), + sizeof(bind_addr)), + SyscallSucceeds()); + + // Let's use a simple IP payload: a UDP datagram. + FileDescriptor udp_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); + SendUDPMessage(udp_sock.get()); + + // Receive and verify the data. + ReceiveMessage(socket_, bind_addr.sll_ifindex); +} + +// Double Bind socket. +TEST_P(CookedPacketTest, DoubleBindSucceeds) { + struct sockaddr_ll bind_addr = {}; + bind_addr.sll_family = AF_PACKET; + bind_addr.sll_protocol = htons(GetParam()); + bind_addr.sll_ifindex = GetLoopbackIndex(); + + ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr), + sizeof(bind_addr)), + SyscallSucceeds()); + + // Binding socket again should fail. + ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr), + sizeof(bind_addr)), + // Linux 4.09 returns EINVAL here, but some time before 4.19 it + // switched to EADDRINUSE. + SyscallSucceeds()); +} + +// Bind and verify we do not receive data on interface which is not bound +TEST_P(CookedPacketTest, BindDrop) { + // Let's use a simple IP payload: a UDP datagram. + FileDescriptor udp_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); + + struct ifaddrs* if_addr_list = nullptr; + auto cleanup = Cleanup([&if_addr_list]() { freeifaddrs(if_addr_list); }); + + ASSERT_THAT(getifaddrs(&if_addr_list), SyscallSucceeds()); + + // Get interface other than loopback. + struct ifreq ifr = {}; + for (struct ifaddrs* i = if_addr_list; i; i = i->ifa_next) { + if (strcmp(i->ifa_name, "lo") != 0) { + strncpy(ifr.ifr_name, i->ifa_name, sizeof(ifr.ifr_name)); + break; + } + } + + // Skip if no interface is available other than loopback. + if (strlen(ifr.ifr_name) == 0) { + GTEST_SKIP(); + } + + // Get interface index. + EXPECT_THAT(ioctl(socket_, SIOCGIFINDEX, &ifr), SyscallSucceeds()); + EXPECT_NE(ifr.ifr_ifindex, 0); + + // Bind to packet socket requires only family, protocol and ifindex. + struct sockaddr_ll bind_addr = {}; + bind_addr.sll_family = AF_PACKET; + bind_addr.sll_protocol = htons(GetParam()); + bind_addr.sll_ifindex = ifr.ifr_ifindex; + + ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr), + sizeof(bind_addr)), + SyscallSucceeds()); + + // Send to loopback interface. + struct sockaddr_in dest = {}; + dest.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + dest.sin_family = AF_INET; + dest.sin_port = kPort; + EXPECT_THAT(sendto(udp_sock.get(), kMessage, sizeof(kMessage), 0, + reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), + SyscallSucceedsWithValue(sizeof(kMessage))); + + // Wait and make sure the socket never receives any data. + struct pollfd pfd = {}; + pfd.fd = socket_; + pfd.events = POLLIN; + EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(0)); +} + +// Verify that we receive outbound packets. This test requires at least one +// non loopback interface so that we can actually capture an outgoing packet. +TEST_P(CookedPacketTest, ReceiveOutbound) { + // Only ETH_P_ALL sockets can receive outbound packets on linux. + SKIP_IF(GetParam() != ETH_P_ALL); + + // Let's use a simple IP payload: a UDP datagram. + FileDescriptor udp_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); + + struct ifaddrs* if_addr_list = nullptr; + auto cleanup = Cleanup([&if_addr_list]() { freeifaddrs(if_addr_list); }); + + ASSERT_THAT(getifaddrs(&if_addr_list), SyscallSucceeds()); + + // Get interface other than loopback. + struct ifreq ifr = {}; + for (struct ifaddrs* i = if_addr_list; i; i = i->ifa_next) { + if (strcmp(i->ifa_name, "lo") != 0) { + strncpy(ifr.ifr_name, i->ifa_name, sizeof(ifr.ifr_name)); + break; + } + } + + // Skip if no interface is available other than loopback. + if (strlen(ifr.ifr_name) == 0) { + GTEST_SKIP(); + } + + // Get interface index and name. + EXPECT_THAT(ioctl(socket_, SIOCGIFINDEX, &ifr), SyscallSucceeds()); + EXPECT_NE(ifr.ifr_ifindex, 0); + int ifindex = ifr.ifr_ifindex; + + constexpr int kMACSize = 6; + char hwaddr[kMACSize]; + // Get interface address. + ASSERT_THAT(ioctl(socket_, SIOCGIFHWADDR, &ifr), SyscallSucceeds()); + ASSERT_THAT(ifr.ifr_hwaddr.sa_family, + AnyOf(Eq(ARPHRD_NONE), Eq(ARPHRD_ETHER))); + memcpy(hwaddr, ifr.ifr_hwaddr.sa_data, kMACSize); + + // Just send it to the google dns server 8.8.8.8. It's UDP we don't care + // if it actually gets to the DNS Server we just want to see that we receive + // it on our AF_PACKET socket. + // + // NOTE: We just want to pick an IP that is non-local to avoid having to + // handle ARP as this should cause the UDP packet to be sent to the default + // gateway configured for the system under test. Otherwise the only packet we + // will see is the ARP query unless we picked an IP which will actually + // resolve. The test is a bit brittle but this was the best compromise for + // now. + struct sockaddr_in dest = {}; + ASSERT_EQ(inet_pton(AF_INET, "8.8.8.8", &dest.sin_addr.s_addr), 1); + dest.sin_family = AF_INET; + dest.sin_port = kPort; + EXPECT_THAT(sendto(udp_sock.get(), kMessage, sizeof(kMessage), 0, + reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), + SyscallSucceedsWithValue(sizeof(kMessage))); + + // Wait and make sure the socket receives the data. + struct pollfd pfd = {}; + pfd.fd = socket_; + pfd.events = POLLIN; + EXPECT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(1)); + + // Now read and check that the packet is the one we just sent. + // Read and verify the data. + constexpr size_t packet_size = + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(kMessage); + char buf[64]; + struct sockaddr_ll src = {}; + socklen_t src_len = sizeof(src); + ASSERT_THAT(recvfrom(socket_, buf, sizeof(buf), 0, + reinterpret_cast<struct sockaddr*>(&src), &src_len), + SyscallSucceedsWithValue(packet_size)); + + // sockaddr_ll ends with an 8 byte physical address field, but ethernet + // addresses only use 6 bytes. Linux used to return sizeof(sockaddr_ll)-2 + // here, but since commit b2cf86e1563e33a14a1c69b3e508d15dc12f804c returns + // sizeof(sockaddr_ll). + ASSERT_THAT(src_len, AnyOf(Eq(sizeof(src)), Eq(sizeof(src) - 2))); + + // Verify the source address. + EXPECT_EQ(src.sll_family, AF_PACKET); + EXPECT_EQ(src.sll_ifindex, ifindex); + EXPECT_EQ(src.sll_halen, ETH_ALEN); + EXPECT_EQ(ntohs(src.sll_protocol), ETH_P_IP); + EXPECT_EQ(src.sll_pkttype, PACKET_OUTGOING); + // Verify the link address of the interface matches that of the non + // non loopback interface address we stored above. + for (int i = 0; i < src.sll_halen; i++) { + EXPECT_EQ(src.sll_addr[i], hwaddr[i]); + } + + // Verify the IP header. + struct iphdr ip = {}; + memcpy(&ip, buf, sizeof(ip)); + EXPECT_EQ(ip.ihl, 5); + EXPECT_EQ(ip.version, 4); + EXPECT_EQ(ip.tot_len, htons(packet_size)); + EXPECT_EQ(ip.protocol, IPPROTO_UDP); + EXPECT_EQ(ip.daddr, dest.sin_addr.s_addr); + EXPECT_NE(ip.saddr, htonl(INADDR_LOOPBACK)); + + // Verify the UDP header. + struct udphdr udp = {}; + memcpy(&udp, buf + sizeof(iphdr), sizeof(udp)); + EXPECT_EQ(udp.dest, kPort); + EXPECT_EQ(udp.len, htons(sizeof(udphdr) + sizeof(kMessage))); + + // Verify the payload. + char* payload = reinterpret_cast<char*>(buf + sizeof(iphdr) + sizeof(udphdr)); + EXPECT_EQ(strncmp(payload, kMessage, sizeof(kMessage)), 0); +} + +// Bind with invalid address. +TEST_P(CookedPacketTest, BindFail) { + // Null address. + ASSERT_THAT( + bind(socket_, nullptr, sizeof(struct sockaddr)), + AnyOf(SyscallFailsWithErrno(EFAULT), SyscallFailsWithErrno(EINVAL))); + + // Address of size 1. + uint8_t addr = 0; + ASSERT_THAT( + bind(socket_, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)), + SyscallFailsWithErrno(EINVAL)); +} + +INSTANTIATE_TEST_SUITE_P(AllInetTests, CookedPacketTest, + ::testing::Values(ETH_P_IP, ETH_P_ALL)); + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/packet_socket_raw.cc b/test/syscalls/linux/packet_socket_raw.cc index 07beb8ba0..aa7182b01 100644 --- a/test/syscalls/linux/packet_socket_raw.cc +++ b/test/syscalls/linux/packet_socket_raw.cc @@ -13,16 +13,13 @@ // limitations under the License. #include <arpa/inet.h> -#include <linux/capability.h> -#include <linux/filter.h> -#include <linux/if_arp.h> -#include <linux/if_packet.h> #include <net/ethernet.h> +#include <net/if_arp.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/udp.h> +#include <netpacket/packet.h> #include <poll.h> -#include <sys/ioctl.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> @@ -30,8 +27,10 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/internal/endian.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/cleanup.h" #include "test/util/file_descriptor.h" #include "test/util/socket_util.h" #include "test/util/test_util.h" @@ -100,7 +99,7 @@ class RawPacketTest : public ::testing::TestWithParam<int> { }; void RawPacketTest::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())) { ASSERT_THAT(socket(AF_PACKET, SOCK_RAW, htons(GetParam())), SyscallFailsWithErrno(EPERM)); GTEST_SKIP(); @@ -150,17 +149,15 @@ void RawPacketTest::SetUp() { void RawPacketTest::TearDown() { // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())) { EXPECT_THAT(close(s_), SyscallSucceeds()); } } int RawPacketTest::GetLoopbackIndex() { - struct ifreq ifr; - snprintf(ifr.ifr_name, IFNAMSIZ, "lo"); - EXPECT_THAT(ioctl(s_, SIOCGIFINDEX, &ifr), SyscallSucceeds()); - EXPECT_NE(ifr.ifr_ifindex, 0); - return ifr.ifr_ifindex; + int v = EXPECT_NO_ERRNO_AND_VALUE(gvisor::testing::GetLoopbackIndex()); + EXPECT_NE(v, 0); + return v; } // Receive via a packet socket. @@ -296,14 +293,6 @@ TEST_P(RawPacketTest, Send) { memcpy(send_buf + sizeof(ethhdr) + sizeof(iphdr) + sizeof(udphdr), kMessage, sizeof(kMessage)); - // We don't implement writing to packet sockets on gVisor. - if (IsRunningOnGvisor()) { - ASSERT_THAT(sendto(s_, send_buf, sizeof(send_buf), 0, - reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), - SyscallFailsWithErrno(EINVAL)); - GTEST_SKIP(); - } - // Send it. ASSERT_THAT(sendto(s_, send_buf, sizeof(send_buf), 0, reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest)), @@ -340,7 +329,7 @@ TEST_P(RawPacketTest, Send) { // Check that setting SO_RCVBUF below min is clamped to the minimum // receive buffer size. TEST_P(RawPacketTest, SetSocketRecvBufBelowMin) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); // Discover minimum receive buf size by trying to set it to zero. // See: @@ -373,7 +362,7 @@ TEST_P(RawPacketTest, SetSocketRecvBufBelowMin) { // Check that setting SO_RCVBUF above max is clamped to the maximum // receive buffer size. TEST_P(RawPacketTest, SetSocketRecvBufAboveMax) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); // Discover max buf size by trying to set the largest possible buffer size. constexpr int kRcvBufSz = 0xffffffff; @@ -400,7 +389,7 @@ TEST_P(RawPacketTest, SetSocketRecvBufAboveMax) { // Check that setting SO_RCVBUF min <= kRcvBufSz <= max is honored. TEST_P(RawPacketTest, SetSocketRecvBuf) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); int max = 0; int min = 0; @@ -449,7 +438,7 @@ TEST_P(RawPacketTest, SetSocketRecvBuf) { // Check that setting SO_SNDBUF below min is clamped to the minimum // receive buffer size. TEST_P(RawPacketTest, SetSocketSendBufBelowMin) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); // Discover minimum buffer size by trying to set it to zero. constexpr int kSndBufSz = 0; @@ -480,7 +469,7 @@ TEST_P(RawPacketTest, SetSocketSendBufBelowMin) { // Check that setting SO_SNDBUF above max is clamped to the maximum // send buffer size. TEST_P(RawPacketTest, SetSocketSendBufAboveMax) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); // Discover maximum buffer size by trying to set it to a large value. constexpr int kSndBufSz = 0xffffffff; @@ -507,7 +496,7 @@ TEST_P(RawPacketTest, SetSocketSendBufAboveMax) { // Check that setting SO_SNDBUF min <= kSndBufSz <= max is honored. TEST_P(RawPacketTest, SetSocketSendBuf) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); int max = 0; int min = 0; @@ -551,7 +540,7 @@ TEST_P(RawPacketTest, SetSocketSendBuf) { } TEST_P(RawPacketTest, GetSocketError) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); int val = 0; socklen_t val_len = sizeof(val); @@ -561,7 +550,7 @@ TEST_P(RawPacketTest, GetSocketError) { } TEST_P(RawPacketTest, GetSocketErrorBind) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); { // Bind to the loopback device. @@ -627,7 +616,7 @@ TEST_P(RawPacketTest, SetSocketDetachFilterNoInstalledFilter) { } TEST_P(RawPacketTest, GetSocketDetachFilter) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); int val = 0; socklen_t val_len = sizeof(val); @@ -636,7 +625,7 @@ TEST_P(RawPacketTest, GetSocketDetachFilter) { } TEST_P(RawPacketTest, SetAndGetSocketLinger) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); int level = SOL_SOCKET; int type = SO_LINGER; @@ -657,7 +646,7 @@ TEST_P(RawPacketTest, SetAndGetSocketLinger) { } TEST_P(RawPacketTest, GetSocketAcceptConn) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); int got = -1; socklen_t length = sizeof(got); @@ -673,7 +662,7 @@ INSTANTIATE_TEST_SUITE_P(AllInetTests, RawPacketTest, class RawPacketMsgSizeTest : public ::testing::TestWithParam<TestAddress> {}; TEST_P(RawPacketMsgSizeTest, SendTooLong) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); TestAddress addr = GetParam().WithPort(kPort); @@ -690,8 +679,11 @@ TEST_P(RawPacketMsgSizeTest, SendTooLong) { SyscallFailsWithErrno(EMSGSIZE)); } +// TODO(https://fxbug.dev/76957): Run this test on Fuchsia once splice is +// available. +#ifndef __Fuchsia__ TEST_P(RawPacketMsgSizeTest, SpliceTooLong) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HavePacketSocketCapability())); const char buf[65536] = {}; int fds[2]; @@ -718,6 +710,7 @@ TEST_P(RawPacketMsgSizeTest, SpliceTooLong) { EXPECT_THAT(n, SyscallSucceedsWithValue(sizeof(buf))); } } +#endif // __Fuchsia__ INSTANTIATE_TEST_SUITE_P(AllRawPacketMsgSizeTest, RawPacketMsgSizeTest, ::testing::Values(V4Loopback(), V6Loopback())); diff --git a/test/syscalls/linux/ping_socket.cc b/test/syscalls/linux/ping_socket.cc index 7ec1938bf..025c9568f 100644 --- a/test/syscalls/linux/ping_socket.cc +++ b/test/syscalls/linux/ping_socket.cc @@ -128,9 +128,6 @@ std::vector<std::tuple<SocketKind, BindTestCase>> ICMPTestCases() { { .bind_to = V4Broadcast(), .want = EADDRNOTAVAIL, - // TODO(gvisor.dev/issue/5711): Remove want_gvisor once ICMP - // sockets are no longer allowed to bind to broadcast addresses. - .want_gvisor = 0, }, { .bind_to = V4Loopback(), @@ -139,9 +136,6 @@ std::vector<std::tuple<SocketKind, BindTestCase>> ICMPTestCases() { { .bind_to = V4LoopbackSubnetBroadcast(), .want = EADDRNOTAVAIL, - // TODO(gvisor.dev/issue/5711): Remove want_gvisor once ICMP - // sockets are no longer allowed to bind to broadcast addresses. - .want_gvisor = 0, }, { .bind_to = V4Multicast(), @@ -155,43 +149,33 @@ std::vector<std::tuple<SocketKind, BindTestCase>> ICMPTestCases() { .bind_to = V4AddrStr("IPv4UnknownUnicast", "192.168.1.1"), .want = EADDRNOTAVAIL, }, - // TODO(gvisor.dev/issue/6021): Remove want_gvisor from all the test - // cases below once ICMP sockets return EAFNOSUPPORT when binding to - // IPv6 addresses. { .bind_to = V6Any(), .want = EAFNOSUPPORT, - .want_gvisor = EINVAL, }, { .bind_to = V6Loopback(), .want = EAFNOSUPPORT, - .want_gvisor = EINVAL, }, { .bind_to = V6Multicast(), .want = EAFNOSUPPORT, - .want_gvisor = EINVAL, }, { .bind_to = V6MulticastInterfaceLocalAllNodes(), .want = EAFNOSUPPORT, - .want_gvisor = EINVAL, }, { .bind_to = V6MulticastLinkLocalAllNodes(), .want = EAFNOSUPPORT, - .want_gvisor = EINVAL, }, { .bind_to = V6MulticastLinkLocalAllRouters(), .want = EAFNOSUPPORT, - .want_gvisor = EINVAL, }, { .bind_to = V6AddrStr("IPv6UnknownUnicast", "fc00::1"), .want = EAFNOSUPPORT, - .want_gvisor = EINVAL, }, }); } diff --git a/test/syscalls/linux/pipe.cc b/test/syscalls/linux/pipe.cc index 0bba86846..209801e36 100644 --- a/test/syscalls/linux/pipe.cc +++ b/test/syscalls/linux/pipe.cc @@ -13,11 +13,13 @@ // limitations under the License. #include <fcntl.h> /* Obtain O_* constant definitions */ +#include <linux/futex.h> #include <linux/magic.h> #include <signal.h> #include <sys/ioctl.h> #include <sys/statfs.h> #include <sys/uio.h> +#include <syscall.h> #include <unistd.h> #include <vector> @@ -50,6 +52,9 @@ std::atomic<int> global_num_signals_received = 0; void SigRecordingHandler(int signum, siginfo_t* siginfo, void* unused_ucontext) { global_num_signals_received++; + ASSERT_THAT(syscall(SYS_futex, &global_num_signals_received, + FUTEX_WAKE | FUTEX_PRIVATE_FLAG, INT_MAX, 0, 0, 0), + SyscallSucceeds()); } PosixErrorOr<Cleanup> RegisterSignalHandler(int signum) { @@ -61,11 +66,14 @@ PosixErrorOr<Cleanup> RegisterSignalHandler(int signum) { return ScopedSigaction(signum, handler); } -void WaitForSignalDelivery(absl::Duration timeout, int max_expected) { - absl::Time wait_start = absl::Now(); - while (global_num_signals_received < max_expected && - absl::Now() - wait_start < timeout) { - absl::SleepFor(absl::Milliseconds(10)); +void WaitForSignalDelivery(int expected) { + while (1) { + int v = global_num_signals_received; + if (v >= expected) { + break; + } + RetryEINTR(syscall)(SYS_futex, &global_num_signals_received, + FUTEX_WAIT | FUTEX_PRIVATE_FLAG, v, 0, 0, 0); } } @@ -371,7 +379,7 @@ TEST_P(PipeTest, ReaderSideCloses) { EXPECT_THAT(write(wfd_.get(), &buf, sizeof(buf)), SyscallFailsWithErrno(EPIPE)); - WaitForSignalDelivery(absl::Seconds(1), 1); + WaitForSignalDelivery(1); ASSERT_EQ(global_num_signals_received, 1); } @@ -411,7 +419,7 @@ TEST_P(PipeTest, BlockWriteClosed) { notify.WaitForNotification(); ASSERT_THAT(close(rfd_.release()), SyscallSucceeds()); - WaitForSignalDelivery(absl::Seconds(1), 1); + WaitForSignalDelivery(1); ASSERT_EQ(global_num_signals_received, 1); t.Join(); @@ -443,7 +451,7 @@ TEST_P(PipeTest, BlockPartialWriteClosed) { // Unblock the above. ASSERT_THAT(close(rfd_.release()), SyscallSucceeds()); - WaitForSignalDelivery(absl::Seconds(1), 2); + WaitForSignalDelivery(2); ASSERT_EQ(global_num_signals_received, 2); t.Join(); diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc index 8a4025fed..cb443163c 100644 --- a/test/syscalls/linux/proc.cc +++ b/test/syscalls/linux/proc.cc @@ -17,7 +17,6 @@ #include <fcntl.h> #include <limits.h> #include <linux/magic.h> -#include <linux/sem.h> #include <sched.h> #include <signal.h> #include <stddef.h> @@ -2453,54 +2452,6 @@ TEST(ProcFilesystems, Bug65172365) { ASSERT_FALSE(proc_filesystems.empty()); } -TEST(ProcFilesystems, PresenceOfShmMaxMniAll) { - uint64_t shmmax = 0; - uint64_t shmall = 0; - uint64_t shmmni = 0; - std::string proc_file; - proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/shmmax")); - ASSERT_FALSE(proc_file.empty()); - ASSERT_TRUE(absl::SimpleAtoi(proc_file, &shmmax)); - proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/shmall")); - ASSERT_FALSE(proc_file.empty()); - ASSERT_TRUE(absl::SimpleAtoi(proc_file, &shmall)); - proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/shmmni")); - ASSERT_FALSE(proc_file.empty()); - ASSERT_TRUE(absl::SimpleAtoi(proc_file, &shmmni)); - - ASSERT_GT(shmmax, 0); - ASSERT_GT(shmall, 0); - ASSERT_GT(shmmni, 0); - ASSERT_LE(shmall, shmmax); - - // These values should never be higher than this by default, for more - // information see uapi/linux/shm.h - ASSERT_LE(shmmax, ULONG_MAX - (1UL << 24)); - ASSERT_LE(shmall, ULONG_MAX - (1UL << 24)); -} - -TEST(ProcFilesystems, PresenceOfSem) { - uint32_t semmsl = 0; - uint32_t semmns = 0; - uint32_t semopm = 0; - uint32_t semmni = 0; - std::string proc_file; - proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/sem")); - ASSERT_FALSE(proc_file.empty()); - std::vector<absl::string_view> sem_limits = - absl::StrSplit(proc_file, absl::ByAnyChar("\t"), absl::SkipWhitespace()); - ASSERT_EQ(sem_limits.size(), 4); - ASSERT_TRUE(absl::SimpleAtoi(sem_limits[0], &semmsl)); - ASSERT_TRUE(absl::SimpleAtoi(sem_limits[1], &semmns)); - ASSERT_TRUE(absl::SimpleAtoi(sem_limits[2], &semopm)); - ASSERT_TRUE(absl::SimpleAtoi(sem_limits[3], &semmni)); - - ASSERT_EQ(semmsl, SEMMSL); - ASSERT_EQ(semmns, SEMMNS); - ASSERT_EQ(semopm, SEMOPM); - ASSERT_EQ(semmni, SEMMNI); -} - // Check that /proc/mounts is a symlink to self/mounts. TEST(ProcMounts, IsSymlink) { auto link = ASSERT_NO_ERRNO_AND_VALUE(ReadLink("/proc/mounts")); diff --git a/test/syscalls/linux/proc_isolated.cc b/test/syscalls/linux/proc_isolated.cc new file mode 100644 index 000000000..38d079d2b --- /dev/null +++ b/test/syscalls/linux/proc_isolated.cc @@ -0,0 +1,100 @@ +// 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. + +#include <linux/msg.h> +#include <linux/sem.h> +#include <linux/shm.h> + +#include "gtest/gtest.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_split.h" +#include "test/util/fs_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { +namespace { + +TEST(ProcDefaults, PresenceOfShmMaxMniAll) { + uint64_t shmmax = 0; + uint64_t shmall = 0; + uint64_t shmmni = 0; + std::string proc_file; + proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/shmmax")); + ASSERT_FALSE(proc_file.empty()); + ASSERT_TRUE(absl::SimpleAtoi(proc_file, &shmmax)); + proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/shmall")); + ASSERT_FALSE(proc_file.empty()); + ASSERT_TRUE(absl::SimpleAtoi(proc_file, &shmall)); + proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/shmmni")); + ASSERT_FALSE(proc_file.empty()); + ASSERT_TRUE(absl::SimpleAtoi(proc_file, &shmmni)); + + ASSERT_EQ(shmmax, SHMMAX); + ASSERT_EQ(shmall, SHMALL); + ASSERT_EQ(shmmni, SHMMNI); + ASSERT_LE(shmall, shmmax); + + // These values should never be higher than this by default, for more + // information see uapi/linux/shm.h + ASSERT_LE(shmmax, ULONG_MAX - (1UL << 24)); + ASSERT_LE(shmall, ULONG_MAX - (1UL << 24)); +} + +TEST(ProcDefaults, PresenceOfSem) { + uint32_t semmsl = 0; + uint32_t semmns = 0; + uint32_t semopm = 0; + uint32_t semmni = 0; + std::string proc_file; + proc_file = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/sys/kernel/sem")); + ASSERT_FALSE(proc_file.empty()); + std::vector<absl::string_view> sem_limits = + absl::StrSplit(proc_file, absl::ByAnyChar("\t"), absl::SkipWhitespace()); + ASSERT_EQ(sem_limits.size(), 4); + ASSERT_TRUE(absl::SimpleAtoi(sem_limits[0], &semmsl)); + ASSERT_TRUE(absl::SimpleAtoi(sem_limits[1], &semmns)); + ASSERT_TRUE(absl::SimpleAtoi(sem_limits[2], &semopm)); + ASSERT_TRUE(absl::SimpleAtoi(sem_limits[3], &semmni)); + + ASSERT_EQ(semmsl, SEMMSL); + ASSERT_EQ(semmns, SEMMNS); + ASSERT_EQ(semopm, SEMOPM); + 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/proc_net.cc b/test/syscalls/linux/proc_net.cc index 4cbe30fc1..672f2dd42 100644 --- a/test/syscalls/linux/proc_net.cc +++ b/test/syscalls/linux/proc_net.cc @@ -152,6 +152,37 @@ TEST(ProcNetDev, Format) { EXPECT_GT(entries.size(), 0); } +// GetMibsAllocationSysctl retuns a value of the net.core.mibs_allocation +// sysctl./proc/sys/net/core/mibs_allocation +// +// When mibs_allocation is unset, a netns creation inherits MIB from init +// network namespace. Otherwise, MIBS is allocated for each namespace. +int GetMibsAllocationSysctl() { + auto ret = GetContents("/proc/sys/net/core/mibs_allocation"); + if (!ret.ok()) { + // The current kernel doesn't support mibs_allocation. + return 1; + } + int32_t val; + EXPECT_TRUE(absl::SimpleAtoi(ret.ValueOrDie(), &val)); + return val; +} + +// GetSNMPMetricFromProc retrieves the metric named `item` of `type` from the +// `snmp` string which represents the file contents of /proc/net/snmp. +// +// Note to test writers: If you are writing tests to check the change in value +// of SNMP metrics, note that if GetMibsAllocationSysctl() == 0 +// (net.core.mibs_allocation isn't set), MIB is from the init network namespace +// and hence these metrics can have system-wide noise. +// +// A feasible way of testing is the following: +// * Consult RFC 1213 to learn the "SYNTAX" of the metric. +// * If net.core.mibs_allocation is set: +// - Can test any metric while checking for hard equality. +// * If net.core.mibs_allocation is NOT set (system-wide noise is present): +// - Only test "SYNTAX Counter" metrics. +// - Counter metrics are increasing in nature, so use LE/GE equality checks. PosixErrorOr<uint64_t> GetSNMPMetricFromProc(const std::string snmp, const std::string& type, const std::string& item) { @@ -226,12 +257,25 @@ TEST(ProcNetSnmp, TcpReset) { newAttemptFails = ASSERT_NO_ERRNO_AND_VALUE( GetSNMPMetricFromProc(snmp, "Tcp", "AttemptFails")); - EXPECT_EQ(oldActiveOpens, newActiveOpens - 1); - EXPECT_EQ(oldOutRsts, newOutRsts - 1); - EXPECT_EQ(oldAttemptFails, newAttemptFails - 1); + if (GetMibsAllocationSysctl()) { + EXPECT_EQ(oldActiveOpens + 1, newActiveOpens); + EXPECT_EQ(oldOutRsts + 1, newOutRsts); + EXPECT_EQ(oldAttemptFails + 1, newAttemptFails); + } else { + // System-wide statistics can have some noise. These metrics should have + // increased by at least 1. + EXPECT_LE(oldActiveOpens + 1, newActiveOpens); + EXPECT_LE(oldOutRsts + 1, newOutRsts); + EXPECT_LE(oldAttemptFails + 1, newAttemptFails); + } } TEST(ProcNetSnmp, TcpEstab) { + // This test aims to test the tcpCurrEstab metric which has "SYNTAX Gauge" as + // per RFC 1213. Hence, it becomes infeasible to test this when system-wide + // statistics have noise. + SKIP_IF(GetMibsAllocationSysctl() == 0); + // TODO(gvisor.dev/issue/866): epsocket metrics are not savable. DisableSave ds; @@ -286,9 +330,9 @@ TEST(ProcNetSnmp, TcpEstab) { newCurrEstab = ASSERT_NO_ERRNO_AND_VALUE( GetSNMPMetricFromProc(snmp, "Tcp", "CurrEstab")); - EXPECT_EQ(oldActiveOpens, newActiveOpens - 1); - EXPECT_EQ(oldPassiveOpens, newPassiveOpens - 1); - EXPECT_EQ(oldCurrEstab, newCurrEstab - 2); + EXPECT_EQ(oldActiveOpens + 1, newActiveOpens); + EXPECT_EQ(oldPassiveOpens + 1, newPassiveOpens); + EXPECT_EQ(oldCurrEstab + 2, newCurrEstab); // Send 1 byte from client to server. ASSERT_THAT(send(s_connect.get(), "a", 1, 0), SyscallSucceedsWithValue(1)); @@ -355,8 +399,15 @@ TEST(ProcNetSnmp, UdpNoPorts) { newNoPorts = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Udp", "NoPorts")); - EXPECT_EQ(oldOutDatagrams, newOutDatagrams - 1); - EXPECT_EQ(oldNoPorts, newNoPorts - 1); + if (GetMibsAllocationSysctl()) { + EXPECT_EQ(oldOutDatagrams + 1, newOutDatagrams); + EXPECT_EQ(oldNoPorts + 1, newNoPorts); + } else { + // System-wide statistics can have some noise. These metrics should have + // increased by at least 1. + EXPECT_LE(oldOutDatagrams + 1, newOutDatagrams); + EXPECT_LE(oldNoPorts + 1, newNoPorts); + } } TEST(ProcNetSnmp, UdpIn) { @@ -405,8 +456,15 @@ TEST(ProcNetSnmp, UdpIn) { newInDatagrams = ASSERT_NO_ERRNO_AND_VALUE( GetSNMPMetricFromProc(snmp, "Udp", "InDatagrams")); - EXPECT_EQ(oldOutDatagrams, newOutDatagrams - 1); - EXPECT_EQ(oldInDatagrams, newInDatagrams - 1); + if (GetMibsAllocationSysctl()) { + EXPECT_EQ(oldOutDatagrams + 1, newOutDatagrams); + EXPECT_EQ(oldInDatagrams + 1, newInDatagrams); + } else { + // System-wide statistics can have some noise. These metrics should have + // increased by at least 1. + EXPECT_LE(oldOutDatagrams + 1, newOutDatagrams); + EXPECT_LE(oldInDatagrams + 1, newInDatagrams); + } } TEST(ProcNetSnmp, CheckNetStat) { diff --git a/test/syscalls/linux/pty.cc b/test/syscalls/linux/pty.cc index 5ff1f12a0..c6592b4a7 100644 --- a/test/syscalls/linux/pty.cc +++ b/test/syscalls/linux/pty.cc @@ -1594,9 +1594,9 @@ TEST_F(JobControlTest, GetForegroundProcessGroupNonControlling) { // - creates a child process in a new process group // - sets that child as the foreground process group // - kills its child and sets itself as the foreground process group. -// TODO(gvisor.dev/issue/5357): Fix and enable. -TEST_F(JobControlTest, DISABLED_SetForegroundProcessGroup) { +TEST_F(JobControlTest, SetForegroundProcessGroup) { auto res = RunInChild([=]() { + TEST_PCHECK(setsid() >= 0); TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); // Ignore SIGTTOU so that we don't stop ourself when calling tcsetpgrp. @@ -1634,12 +1634,90 @@ TEST_F(JobControlTest, DISABLED_SetForegroundProcessGroup) { // Set ourself as the foreground process. pid_t pgid; - TEST_PCHECK(pgid = getpgid(0) == 0); + TEST_PCHECK((pgid = getpgid(0)) >= 0); TEST_PCHECK(!tcsetpgrp(replica_.get(), pgid)); }); ASSERT_NO_ERRNO(res); } +// This test verifies if a SIGTTOU signal is sent to the calling process's group +// when tcsetpgrp is called by a background process +TEST_F(JobControlTest, SetForegroundProcessGroupSIGTTOUBackground) { + auto res = RunInChild([=]() { + TEST_PCHECK(setsid() >= 0); + TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); + pid_t grandchild = fork(); + if (!grandchild) { + // Assign a different pgid to the child so it will result as + // a background process. + TEST_PCHECK(!setpgid(grandchild, getpid())); + TEST_PCHECK(!tcsetpgrp(replica_.get(), getpgid(0))); + // We should never reach this. + _exit(1); + } + int wstatus; + TEST_PCHECK(waitpid(grandchild, &wstatus, WSTOPPED) == grandchild); + TEST_PCHECK(WSTOPSIG(wstatus) == SIGTTOU); + }); + ASSERT_NO_ERRNO(res); +} + +// This test verifies that a SIGTTOU signal is not delivered to +// a background process which calls tcsetpgrp and is ignoring SIGTTOU +TEST_F(JobControlTest, SetForegroundProcessGroupSIGTTOUIgnored) { + auto res = RunInChild([=]() { + TEST_PCHECK(setsid() >= 0); + TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); + pid_t grandchild = fork(); + if (!grandchild) { + // Ignore SIGTTOU so the child in background won't + // be stopped when it will call tcsetpgrp + struct sigaction sa = {}; + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGTTOU, &sa, NULL); + // Assign a different pgid to the child so it will result as + // a background process. + TEST_PCHECK(!setpgid(grandchild, getpid())); + TEST_PCHECK(!tcsetpgrp(replica_.get(), getpgid(0))); + _exit(0); + } + int wstatus; + TEST_PCHECK(waitpid(grandchild, &wstatus, WSTOPPED) == grandchild); + TEST_PCHECK(WSTOPSIG(wstatus) != SIGTTOU); + TEST_PCHECK(WIFEXITED(wstatus)); + }); + ASSERT_NO_ERRNO(res); +} + +// This test verifies that a SIGTTOU signal is not delivered to +// a background process which calls tcsetpgrp and is blocking SIGTTOU +TEST_F(JobControlTest, SetForegroundProcessGroupSIGTTOUBlocked) { + auto res = RunInChild([=]() { + TEST_PCHECK(setsid() >= 0); + TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); + pid_t grandchild = fork(); + if (!grandchild) { + // Block SIGTTOU so the child in background won't + // be stopped when it will call tcsetpgrp + sigset_t signal_set; + sigemptyset(&signal_set); + sigaddset(&signal_set, SIGTTOU); + sigprocmask(SIG_BLOCK, &signal_set, NULL); + // Assign a different pgid to the child so it will result as + // a background process. + TEST_PCHECK(!setpgid(grandchild, getpid())); + TEST_PCHECK(!tcsetpgrp(replica_.get(), getpgid(0))); + _exit(0); + } + int wstatus; + TEST_PCHECK(waitpid(grandchild, &wstatus, WSTOPPED) == grandchild); + TEST_PCHECK(WSTOPSIG(wstatus) != SIGTTOU); + }); + ASSERT_NO_ERRNO(res); +} + TEST_F(JobControlTest, SetForegroundProcessGroupWrongTTY) { pid_t pid = getpid(); ASSERT_THAT(ioctl(replica_.get(), TIOCSPGRP, &pid), @@ -1657,9 +1735,9 @@ TEST_F(JobControlTest, SetForegroundProcessGroupNegPgid) { ASSERT_NO_ERRNO(ret); } -// TODO(gvisor.dev/issue/5357): Fix and enable. -TEST_F(JobControlTest, DISABLED_SetForegroundProcessGroupEmptyProcessGroup) { +TEST_F(JobControlTest, SetForegroundProcessGroupEmptyProcessGroup) { auto res = RunInChild([=]() { + TEST_PCHECK(setsid() >= 0); TEST_PCHECK(!ioctl(replica_.get(), TIOCSCTTY, 0)); // Create a new process, put it in a new process group, make that group the diff --git a/test/syscalls/linux/raw_socket.cc b/test/syscalls/linux/raw_socket.cc index e19fe8f6b..ef1db47ee 100644 --- a/test/syscalls/linux/raw_socket.cc +++ b/test/syscalls/linux/raw_socket.cc @@ -13,8 +13,6 @@ // limitations under the License. #include <arpa/inet.h> -#include <linux/capability.h> -#include <linux/filter.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip6.h> @@ -99,7 +97,7 @@ class RawSocketTest : public ::testing::TestWithParam<std::tuple<int, int>> { }; void RawSocketTest::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) { ASSERT_THAT(socket(Family(), SOCK_RAW, Protocol()), SyscallFailsWithErrno(EPERM)); GTEST_SKIP(); @@ -123,7 +121,7 @@ void RawSocketTest::SetUp() { void RawSocketTest::TearDown() { // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) { EXPECT_THAT(close(s_), SyscallSucceeds()); } } @@ -132,7 +130,7 @@ void RawSocketTest::TearDown() { // BasicRawSocket::Setup creates the first one, so we only have to create one // more here. TEST_P(RawSocketTest, MultipleCreation) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int s2; ASSERT_THAT(s2 = socket(Family(), SOCK_RAW, Protocol()), SyscallSucceeds()); @@ -142,7 +140,7 @@ TEST_P(RawSocketTest, MultipleCreation) { // Test that shutting down an unconnected socket fails. TEST_P(RawSocketTest, FailShutdownWithoutConnect) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT(shutdown(s_, SHUT_WR), SyscallFailsWithErrno(ENOTCONN)); ASSERT_THAT(shutdown(s_, SHUT_RD), SyscallFailsWithErrno(ENOTCONN)); @@ -150,7 +148,7 @@ TEST_P(RawSocketTest, FailShutdownWithoutConnect) { // Shutdown is a no-op for raw sockets (and datagram sockets in general). TEST_P(RawSocketTest, ShutdownWriteNoop) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), @@ -165,7 +163,7 @@ TEST_P(RawSocketTest, ShutdownWriteNoop) { // Shutdown is a no-op for raw sockets (and datagram sockets in general). TEST_P(RawSocketTest, ShutdownReadNoop) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), @@ -182,14 +180,14 @@ TEST_P(RawSocketTest, ShutdownReadNoop) { // Test that listen() fails. TEST_P(RawSocketTest, FailListen) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT(listen(s_, 1), SyscallFailsWithErrno(ENOTSUP)); } // Test that accept() fails. TEST_P(RawSocketTest, FailAccept) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); struct sockaddr saddr; socklen_t addrlen; @@ -197,7 +195,7 @@ TEST_P(RawSocketTest, FailAccept) { } TEST_P(RawSocketTest, BindThenGetSockName) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_); ASSERT_THAT(bind(s_, addr, AddrLen()), SyscallSucceeds()); @@ -221,7 +219,7 @@ TEST_P(RawSocketTest, BindThenGetSockName) { } TEST_P(RawSocketTest, ConnectThenGetSockName) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_); ASSERT_THAT(connect(s_, addr, AddrLen()), SyscallSucceeds()); @@ -246,7 +244,7 @@ TEST_P(RawSocketTest, ConnectThenGetSockName) { // Test that getpeername() returns nothing before connect(). TEST_P(RawSocketTest, FailGetPeerNameBeforeConnect) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); struct sockaddr saddr; socklen_t addrlen = sizeof(saddr); @@ -256,7 +254,7 @@ TEST_P(RawSocketTest, FailGetPeerNameBeforeConnect) { // Test that getpeername() returns something after connect(). TEST_P(RawSocketTest, GetPeerName) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), @@ -270,7 +268,7 @@ TEST_P(RawSocketTest, GetPeerName) { // Test that the socket is writable immediately. TEST_P(RawSocketTest, PollWritableImmediately) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); struct pollfd pfd = {}; pfd.fd = s_; @@ -280,7 +278,7 @@ TEST_P(RawSocketTest, PollWritableImmediately) { // Test that the socket isn't readable before receiving anything. TEST_P(RawSocketTest, PollNotReadableInitially) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Try to receive data with MSG_DONTWAIT, which returns immediately if there's // nothing to be read. @@ -291,7 +289,7 @@ TEST_P(RawSocketTest, PollNotReadableInitially) { // Test that the socket becomes readable once something is written to it. TEST_P(RawSocketTest, PollTriggeredOnWrite) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Write something so that there's data to be read. // Arbitrary. @@ -306,7 +304,7 @@ TEST_P(RawSocketTest, PollTriggeredOnWrite) { // Test that we can connect() to a valid IP (loopback). TEST_P(RawSocketTest, ConnectToLoopback) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), @@ -315,7 +313,7 @@ TEST_P(RawSocketTest, ConnectToLoopback) { // Test that calling send() without connect() fails. TEST_P(RawSocketTest, SendWithoutConnectFails) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Arbitrary. constexpr char kBuf[] = "Endgame was good"; @@ -325,7 +323,7 @@ TEST_P(RawSocketTest, SendWithoutConnectFails) { // Wildcard Bind. TEST_P(RawSocketTest, BindToWildcard) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); struct sockaddr_storage addr; addr = {}; @@ -346,16 +344,15 @@ TEST_P(RawSocketTest, BindToWildcard) { // Bind to localhost. TEST_P(RawSocketTest, BindToLocalhost) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); + ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), + SyscallSucceeds()); } // Bind to a different address. TEST_P(RawSocketTest, BindToInvalid) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); struct sockaddr_storage bind_addr = addr_; if (Family() == AF_INET) { @@ -367,13 +364,14 @@ TEST_P(RawSocketTest, BindToInvalid) { memset(&sin6->sin6_addr.s6_addr, 0, sizeof(sin6->sin6_addr.s6_addr)); sin6->sin6_addr.s6_addr[0] = 1; // 1: - An address that we can't bind to. } - ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr), - AddrLen()), SyscallFailsWithErrno(EADDRNOTAVAIL)); + ASSERT_THAT( + bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr), AddrLen()), + SyscallFailsWithErrno(EADDRNOTAVAIL)); } // Send and receive an packet. TEST_P(RawSocketTest, SendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Arbitrary. constexpr char kBuf[] = "TB12"; @@ -388,7 +386,7 @@ TEST_P(RawSocketTest, SendAndReceive) { // We should be able to create multiple raw sockets for the same protocol and // receive the same packet on both. TEST_P(RawSocketTest, MultipleSocketReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int s2; ASSERT_THAT(s2 = socket(Family(), SOCK_RAW, Protocol()), SyscallSucceeds()); @@ -403,11 +401,11 @@ TEST_P(RawSocketTest, MultipleSocketReceive) { // Receive it on socket 2. std::vector<char> recv_buf2(sizeof(kBuf) + HdrLen()); - ASSERT_NO_FATAL_FAILURE(ReceiveBufFrom(s2, recv_buf2.data(), - recv_buf2.size())); + ASSERT_NO_FATAL_FAILURE( + ReceiveBufFrom(s2, recv_buf2.data(), recv_buf2.size())); - EXPECT_EQ(memcmp(recv_buf1.data() + HdrLen(), - recv_buf2.data() + HdrLen(), sizeof(kBuf)), + EXPECT_EQ(memcmp(recv_buf1.data() + HdrLen(), recv_buf2.data() + HdrLen(), + sizeof(kBuf)), 0); ASSERT_THAT(close(s2), SyscallSucceeds()); @@ -415,7 +413,7 @@ TEST_P(RawSocketTest, MultipleSocketReceive) { // Test that connect sends packets to the right place. TEST_P(RawSocketTest, SendAndReceiveViaConnect) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), @@ -434,11 +432,10 @@ TEST_P(RawSocketTest, SendAndReceiveViaConnect) { // Bind to localhost, then send and receive packets. TEST_P(RawSocketTest, BindSendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); + ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), + SyscallSucceeds()); // Arbitrary. constexpr char kBuf[] = "DR16"; @@ -452,11 +449,10 @@ TEST_P(RawSocketTest, BindSendAndReceive) { // Bind and connect to localhost and send/receive packets. TEST_P(RawSocketTest, BindConnectSendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); + ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), + SyscallSucceeds()); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), SyscallSucceeds()); @@ -474,7 +470,7 @@ TEST_P(RawSocketTest, BindConnectSendAndReceive) { // Check that setting SO_RCVBUF below min is clamped to the minimum // receive buffer size. TEST_P(RawSocketTest, SetSocketRecvBufBelowMin) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Discover minimum receive buf size by trying to set it to zero. // See: @@ -507,7 +503,7 @@ TEST_P(RawSocketTest, SetSocketRecvBufBelowMin) { // Check that setting SO_RCVBUF above max is clamped to the maximum // receive buffer size. TEST_P(RawSocketTest, SetSocketRecvBufAboveMax) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Discover max buf size by trying to set the largest possible buffer size. constexpr int kRcvBufSz = 0xffffffff; @@ -534,7 +530,7 @@ TEST_P(RawSocketTest, SetSocketRecvBufAboveMax) { // Check that setting SO_RCVBUF min <= kRcvBufSz <= max is honored. TEST_P(RawSocketTest, SetSocketRecvBuf) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int max = 0; int min = 0; @@ -584,7 +580,7 @@ TEST_P(RawSocketTest, SetSocketRecvBuf) { // Check that setting SO_SNDBUF below min is clamped to the minimum // receive buffer size. TEST_P(RawSocketTest, SetSocketSendBufBelowMin) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Discover minimum buffer size by trying to set it to zero. constexpr int kSndBufSz = 0; @@ -615,7 +611,7 @@ TEST_P(RawSocketTest, SetSocketSendBufBelowMin) { // Check that setting SO_SNDBUF above max is clamped to the maximum // send buffer size. TEST_P(RawSocketTest, SetSocketSendBufAboveMax) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Discover maximum buffer size by trying to set it to a large value. constexpr int kSndBufSz = 0xffffffff; @@ -642,7 +638,7 @@ TEST_P(RawSocketTest, SetSocketSendBufAboveMax) { // Check that setting SO_SNDBUF min <= kSndBufSz <= max is honored. TEST_P(RawSocketTest, SetSocketSendBuf) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int max = 0; int min = 0; @@ -688,11 +684,10 @@ TEST_P(RawSocketTest, SetSocketSendBuf) { // Test that receive buffer limits are not enforced when the recv buffer is // empty. TEST_P(RawSocketTest, RecvBufLimitsEmptyRecvBuffer) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); + ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), + SyscallSucceeds()); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), SyscallSucceeds()); @@ -719,9 +714,7 @@ TEST_P(RawSocketTest, RecvBufLimitsEmptyRecvBuffer) { // Receive the packet and make sure it's identical. std::vector<char> recv_buf(buf.size() + HdrLen()); ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size())); - EXPECT_EQ( - memcmp(recv_buf.data() + HdrLen(), buf.data(), buf.size()), - 0); + EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), buf.data(), buf.size()), 0); } { @@ -734,9 +727,7 @@ TEST_P(RawSocketTest, RecvBufLimitsEmptyRecvBuffer) { // Receive the packet and make sure it's identical. std::vector<char> recv_buf(buf.size() + HdrLen()); ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size())); - EXPECT_EQ( - memcmp(recv_buf.data() + HdrLen(), buf.data(), buf.size()), - 0); + EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), buf.data(), buf.size()), 0); } } @@ -750,11 +741,10 @@ TEST_P(RawSocketTest, RecvBufLimits) { if (Protocol() == IPPROTO_TCP) { return; } - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); - ASSERT_THAT( - bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), - SyscallSucceeds()); + ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), + SyscallSucceeds()); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), AddrLen()), SyscallSucceeds()); @@ -814,9 +804,7 @@ TEST_P(RawSocketTest, RecvBufLimits) { // Receive the packet and make sure it's identical. std::vector<char> recv_buf(buf.size() + HdrLen()); ASSERT_NO_FATAL_FAILURE(ReceiveBuf(recv_buf.data(), recv_buf.size())); - EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), buf.data(), - buf.size()), - 0); + EXPECT_EQ(memcmp(recv_buf.data() + HdrLen(), buf.data(), buf.size()), 0); } // Assert that the last packet is dropped because the receive buffer should @@ -885,7 +873,7 @@ TEST_P(RawSocketTest, GetSocketDetachFilter) { // AF_INET6+SOCK_RAW+IPPROTO_RAW sockets can be created, but not written to. TEST(RawSocketTest, IPv6ProtoRaw) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int sock; ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW), @@ -902,7 +890,7 @@ TEST(RawSocketTest, IPv6ProtoRaw) { } TEST(RawSocketTest, IPv6SendMsg) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int sock; ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP), @@ -930,7 +918,7 @@ TEST(RawSocketTest, IPv6SendMsg) { } TEST_P(RawSocketTest, ConnectOnIPv6Socket) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int sock; ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP), @@ -951,6 +939,124 @@ INSTANTIATE_TEST_SUITE_P( ::testing::Combine(::testing::Values(IPPROTO_TCP, IPPROTO_UDP), ::testing::Values(AF_INET, AF_INET6))); +void TestRawSocketMaybeBindReceive(bool do_bind) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); + + constexpr char payload[] = "abcdefgh"; + + const sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr = {.s_addr = htonl(INADDR_LOOPBACK)}, + }; + + FileDescriptor udp_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0)); + sockaddr_in udp_sock_bind_addr = addr; + socklen_t udp_sock_bind_addr_len = sizeof(udp_sock_bind_addr); + ASSERT_THAT(bind(udp_sock.get(), + reinterpret_cast<const sockaddr*>(&udp_sock_bind_addr), + sizeof(udp_sock_bind_addr)), + SyscallSucceeds()); + ASSERT_THAT(getsockname(udp_sock.get(), + reinterpret_cast<sockaddr*>(&udp_sock_bind_addr), + &udp_sock_bind_addr_len), + SyscallSucceeds()); + ASSERT_EQ(udp_sock_bind_addr_len, sizeof(udp_sock_bind_addr)); + + FileDescriptor raw_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_UDP)); + + auto test_recv = [&](const char* scope, uint32_t expected_destination) { + SCOPED_TRACE(scope); + + constexpr int kInfinitePollTimeout = -1; + pollfd pfd = { + .fd = raw_sock.get(), + .events = POLLIN, + }; + ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kInfinitePollTimeout), + SyscallSucceedsWithValue(1)); + + struct ipv4_udp_packet { + iphdr ip; + udphdr udp; + char data[sizeof(payload)]; + + // Used to make sure only the required space is used. + char unused_space; + } ABSL_ATTRIBUTE_PACKED; + constexpr size_t kExpectedIPPacketSize = + offsetof(ipv4_udp_packet, unused_space); + + // Receive the whole IPv4 packet on the raw socket. + ipv4_udp_packet read_raw_packet; + sockaddr_in peer; + socklen_t peerlen = sizeof(peer); + ASSERT_EQ( + recvfrom(raw_sock.get(), reinterpret_cast<char*>(&read_raw_packet), + sizeof(read_raw_packet), 0 /* flags */, + reinterpret_cast<sockaddr*>(&peer), &peerlen), + static_cast<ssize_t>(kExpectedIPPacketSize)) + << strerror(errno); + ASSERT_EQ(peerlen, sizeof(peer)); + EXPECT_EQ(read_raw_packet.ip.version, static_cast<unsigned int>(IPVERSION)); + // IHL holds the number of header bytes in 4 byte units. + EXPECT_EQ(read_raw_packet.ip.ihl, sizeof(read_raw_packet.ip) / 4); + EXPECT_EQ(ntohs(read_raw_packet.ip.tot_len), kExpectedIPPacketSize); + EXPECT_EQ(ntohs(read_raw_packet.ip.frag_off) & IP_OFFMASK, 0); + EXPECT_EQ(read_raw_packet.ip.protocol, SOL_UDP); + EXPECT_EQ(ntohl(read_raw_packet.ip.saddr), INADDR_LOOPBACK); + EXPECT_EQ(ntohl(read_raw_packet.ip.daddr), expected_destination); + EXPECT_EQ(read_raw_packet.udp.source, udp_sock_bind_addr.sin_port); + EXPECT_EQ(read_raw_packet.udp.dest, udp_sock_bind_addr.sin_port); + EXPECT_EQ(ntohs(read_raw_packet.udp.len), + kExpectedIPPacketSize - sizeof(read_raw_packet.ip)); + for (size_t i = 0; i < sizeof(payload); i++) { + EXPECT_EQ(read_raw_packet.data[i], payload[i]) + << "byte mismatch @ idx=" << i; + } + EXPECT_EQ(peer.sin_family, AF_INET); + EXPECT_EQ(peer.sin_port, 0); + EXPECT_EQ(ntohl(peer.sin_addr.s_addr), INADDR_LOOPBACK); + }; + + if (do_bind) { + ASSERT_THAT(bind(raw_sock.get(), reinterpret_cast<const sockaddr*>(&addr), + sizeof(addr)), + SyscallSucceeds()); + } + + constexpr int kSendToFlags = 0; + sockaddr_in different_addr = udp_sock_bind_addr; + different_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK + 1); + ASSERT_THAT(sendto(udp_sock.get(), payload, sizeof(payload), kSendToFlags, + reinterpret_cast<const sockaddr*>(&different_addr), + sizeof(different_addr)), + SyscallSucceedsWithValue(sizeof(payload))); + if (!do_bind) { + ASSERT_NO_FATAL_FAILURE( + test_recv("different_addr", ntohl(different_addr.sin_addr.s_addr))); + } + ASSERT_THAT(sendto(udp_sock.get(), payload, sizeof(payload), kSendToFlags, + reinterpret_cast<const sockaddr*>(&udp_sock_bind_addr), + sizeof(udp_sock_bind_addr)), + SyscallSucceedsWithValue(sizeof(payload))); + ASSERT_NO_FATAL_FAILURE( + test_recv("addr", ntohl(udp_sock_bind_addr.sin_addr.s_addr))); +} + +TEST(RawSocketTest, UnboundReceive) { + // Test that a raw socket receives packets destined to any address if it is + // not bound to an address. + ASSERT_NO_FATAL_FAILURE(TestRawSocketMaybeBindReceive(false /* do_bind */)); +} + +TEST(RawSocketTest, BindReceive) { + // Test that a raw socket only receives packets destined to the address it is + // bound to. + ASSERT_NO_FATAL_FAILURE(TestRawSocketMaybeBindReceive(true /* do_bind */)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/raw_socket_hdrincl.cc b/test/syscalls/linux/raw_socket_hdrincl.cc index f1d8fd295..d45bd07bc 100644 --- a/test/syscalls/linux/raw_socket_hdrincl.cc +++ b/test/syscalls/linux/raw_socket_hdrincl.cc @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <linux/capability.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> @@ -63,7 +62,7 @@ class RawHDRINCL : public ::testing::Test { }; void RawHDRINCL::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) { ASSERT_THAT(socket(AF_INET, SOCK_RAW, IPPROTO_RAW), SyscallFailsWithErrno(EPERM)); GTEST_SKIP(); @@ -81,7 +80,7 @@ void RawHDRINCL::SetUp() { void RawHDRINCL::TearDown() { // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) { EXPECT_THAT(close(socket_), SyscallSucceeds()); } } diff --git a/test/syscalls/linux/raw_socket_icmp.cc b/test/syscalls/linux/raw_socket_icmp.cc index 27d3fffee..3f9717284 100644 --- a/test/syscalls/linux/raw_socket_icmp.cc +++ b/test/syscalls/linux/raw_socket_icmp.cc @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <linux/capability.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> @@ -77,7 +76,7 @@ class RawSocketICMPTest : public ::testing::Test { }; void RawSocketICMPTest::SetUp() { - if (!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) { ASSERT_THAT(socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), SyscallFailsWithErrno(EPERM)); GTEST_SKIP(); @@ -95,7 +94,7 @@ void RawSocketICMPTest::SetUp() { void RawSocketICMPTest::TearDown() { // TearDown will be run even if we skip the test. - if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))) { + if (ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) { EXPECT_THAT(close(s_), SyscallSucceeds()); } } @@ -103,7 +102,7 @@ void RawSocketICMPTest::TearDown() { // We'll only read an echo in this case, as the kernel won't respond to the // malformed ICMP checksum. TEST_F(RawSocketICMPTest, SendAndReceiveBadChecksum) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Prepare and send an ICMP packet. Use arbitrary junk for checksum, sequence, // and ID. None of that should matter for raw sockets - the kernel should @@ -132,7 +131,7 @@ TEST_F(RawSocketICMPTest, SendAndReceiveBadChecksum) { // Send and receive an ICMP packet. TEST_F(RawSocketICMPTest, SendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); // Prepare and send an ICMP packet. Use arbitrary junk for sequence and ID. // None of that should matter for raw sockets - the kernel should still give @@ -152,7 +151,7 @@ TEST_F(RawSocketICMPTest, SendAndReceive) { // We should be able to create multiple raw sockets for the same protocol and // receive the same packet on both. TEST_F(RawSocketICMPTest, MultipleSocketReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); FileDescriptor s2 = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)); @@ -215,7 +214,7 @@ TEST_F(RawSocketICMPTest, MultipleSocketReceive) { // A raw ICMP socket and ping socket should both receive the ICMP packets // intended for the ping socket. TEST_F(RawSocketICMPTest, RawAndPingSockets) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); FileDescriptor ping_sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)); @@ -265,7 +264,7 @@ TEST_F(RawSocketICMPTest, RawAndPingSockets) { // while a ping socket should not. Neither should be able to receieve a short // malformed packet. TEST_F(RawSocketICMPTest, ShortEchoRawAndPingSockets) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); FileDescriptor ping_sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)); @@ -306,7 +305,7 @@ TEST_F(RawSocketICMPTest, ShortEchoRawAndPingSockets) { // while ping socket should not. // Neither should be able to receieve a short malformed packet. TEST_F(RawSocketICMPTest, ShortEchoReplyRawAndPingSockets) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); FileDescriptor ping_sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)); @@ -345,7 +344,7 @@ TEST_F(RawSocketICMPTest, ShortEchoReplyRawAndPingSockets) { // Test that connect() sends packets to the right place. TEST_F(RawSocketICMPTest, SendAndReceiveViaConnect) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), @@ -369,7 +368,7 @@ TEST_F(RawSocketICMPTest, SendAndReceiveViaConnect) { // Bind to localhost, then send and receive packets. TEST_F(RawSocketICMPTest, BindSendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), @@ -392,7 +391,7 @@ TEST_F(RawSocketICMPTest, BindSendAndReceive) { // Bind and connect to localhost and send/receive packets. TEST_F(RawSocketICMPTest, BindConnectSendAndReceive) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); ASSERT_THAT( bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), @@ -418,7 +417,7 @@ TEST_F(RawSocketICMPTest, BindConnectSendAndReceive) { // Set and get SO_LINGER. TEST_F(RawSocketICMPTest, SetAndGetSocketLinger) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int level = SOL_SOCKET; int type = SO_LINGER; @@ -440,7 +439,7 @@ TEST_F(RawSocketICMPTest, SetAndGetSocketLinger) { // Test getsockopt for SO_ACCEPTCONN. TEST_F(RawSocketICMPTest, GetSocketAcceptConn) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); int got = -1; socklen_t length = sizeof(got); diff --git a/test/syscalls/linux/sendfile.cc b/test/syscalls/linux/sendfile.cc index bea4ee71c..9bd3bd5e8 100644 --- a/test/syscalls/linux/sendfile.cc +++ b/test/syscalls/linux/sendfile.cc @@ -208,38 +208,6 @@ TEST(SendFileTest, SendAndUpdateFileOffset) { absl::string_view(actual, kHalfDataSize)); } -TEST(SendFileTest, SendToDevZeroAndUpdateFileOffset) { - // Create temp files. - // Test input string length must be > 2 AND even. - constexpr char kData[] = "The slings and arrows of outrageous fortune,"; - constexpr int kDataSize = sizeof(kData) - 1; - constexpr int kHalfDataSize = kDataSize / 2; - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), kData, TempPath::kDefaultFileMode)); - - // Open the input file as read only. - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY)); - - // Open /dev/zero as write only. - const FileDescriptor outf = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_WRONLY)); - - // Send data and verify that sendfile returns the correct value. - int bytes_sent; - EXPECT_THAT( - bytes_sent = sendfile(outf.get(), inf.get(), nullptr, kHalfDataSize), - SyscallSucceedsWithValue(kHalfDataSize)); - - char actual[kHalfDataSize]; - // Verify that the input file offset has been updated. - ASSERT_THAT(read(inf.get(), &actual, kDataSize - bytes_sent), - SyscallSucceedsWithValue(kHalfDataSize)); - EXPECT_EQ( - absl::string_view(kData + kDataSize - bytes_sent, kDataSize - bytes_sent), - absl::string_view(actual, kHalfDataSize)); -} - TEST(SendFileTest, SendAndUpdateFileOffsetFromNonzeroStartingPoint) { // Create temp files. // Test input string length must be > 2 AND divisible by 4. @@ -609,23 +577,6 @@ TEST(SendFileTest, SendPipeBlocks) { SyscallSucceedsWithValue(kDataSize)); } -TEST(SendFileTest, SendToSpecialFile) { - // Create temp file. - const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( - GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); - - const FileDescriptor inf = - ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDWR)); - constexpr int kSize = 0x7ff; - ASSERT_THAT(ftruncate(inf.get(), kSize), SyscallSucceeds()); - - auto eventfd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD()); - - // eventfd can accept a number of bytes which is a multiple of 8. - EXPECT_THAT(sendfile(eventfd.get(), inf.get(), nullptr, 0xfffff), - SyscallSucceedsWithValue(kSize & (~7))); -} - TEST(SendFileTest, SendFileToPipe) { // Create temp file. constexpr char kData[] = "<insert-quote-here>"; @@ -672,57 +623,6 @@ TEST(SendFileTest, SendFileToSelf) { SyscallSucceedsWithValue(kSendfileSize)); } -static volatile int signaled = 0; -void SigUsr1Handler(int sig, siginfo_t* info, void* context) { signaled = 1; } - -TEST(SendFileTest, ToEventFDDoesNotSpin) { - FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0)); - - // Write the maximum value of an eventfd to a file. - const uint64_t kMaxEventfdValue = 0xfffffffffffffffe; - const auto tempfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); - const auto tempfd = ASSERT_NO_ERRNO_AND_VALUE(Open(tempfile.path(), O_RDWR)); - ASSERT_THAT( - pwrite(tempfd.get(), &kMaxEventfdValue, sizeof(kMaxEventfdValue), 0), - SyscallSucceedsWithValue(sizeof(kMaxEventfdValue))); - - // Set the eventfd's value to 1. - const uint64_t kOne = 1; - ASSERT_THAT(write(efd.get(), &kOne, sizeof(kOne)), - SyscallSucceedsWithValue(sizeof(kOne))); - - // Set up signal handler. - struct sigaction sa = {}; - sa.sa_sigaction = SigUsr1Handler; - sa.sa_flags = SA_SIGINFO; - const auto cleanup_sigact = - ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa)); - - // Send SIGUSR1 to this thread in 1 second. - struct sigevent sev = {}; - sev.sigev_notify = SIGEV_THREAD_ID; - sev.sigev_signo = SIGUSR1; - sev.sigev_notify_thread_id = gettid(); - auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev)); - struct itimerspec its = {}; - its.it_value = absl::ToTimespec(absl::Seconds(1)); - DisableSave ds; // Asserting an EINTR. - ASSERT_NO_ERRNO(timer.Set(0, its)); - - // Sendfile from tempfd to the eventfd. Since the eventfd is not already at - // its maximum value, the eventfd is "ready for writing"; however, since the - // eventfd's existing value plus the new value would exceed the maximum, the - // write should internally fail with EWOULDBLOCK. In this case, sendfile() - // should block instead of spinning, and eventually be interrupted by our - // timer. See b/172075629. - EXPECT_THAT( - sendfile(efd.get(), tempfd.get(), nullptr, sizeof(kMaxEventfdValue)), - SyscallFailsWithErrno(EINTR)); - - // Signal should have been handled. - EXPECT_EQ(signaled, 1); -} - } // 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_datagram_based_socket_unbound.cc b/test/syscalls/linux/socket_ipv4_datagram_based_socket_unbound.cc new file mode 100644 index 000000000..0e3e7057b --- /dev/null +++ b/test/syscalls/linux/socket_ipv4_datagram_based_socket_unbound.cc @@ -0,0 +1,299 @@ +// Copyright 2019 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_ipv4_datagram_based_socket_unbound.h" + +#include "gtest/gtest.h" +#include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/util/capability_util.h" + +namespace gvisor { +namespace testing { + +void IPv4DatagramBasedUnboundSocketTest::SetUp() { + if (GetParam().type & SOCK_RAW) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())); + } +} + +// Check that dropping a group membership that does not exist fails. +TEST_P(IPv4DatagramBasedUnboundSocketTest, IpMulticastInvalidDrop) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + // Unregister from a membership that we didn't have. + ip_mreq group = {}; + group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); + group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_DROP_MEMBERSHIP, &group, + sizeof(group)), + SyscallFailsWithErrno(EADDRNOTAVAIL)); +} + +TEST_P(IPv4DatagramBasedUnboundSocketTest, IpMulticastIfZero) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + ip_mreqn iface = {}; + EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, + sizeof(iface)), + SyscallSucceeds()); +} + +TEST_P(IPv4DatagramBasedUnboundSocketTest, IpMulticastIfInvalidNic) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + ip_mreqn iface = {}; + iface.imr_ifindex = -1; + EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, + sizeof(iface)), + SyscallFailsWithErrno(EADDRNOTAVAIL)); +} + +TEST_P(IPv4DatagramBasedUnboundSocketTest, IpMulticastIfInvalidAddr) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + ip_mreq iface = {}; + iface.imr_interface.s_addr = inet_addr("255.255.255"); + EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, + sizeof(iface)), + SyscallFailsWithErrno(EADDRNOTAVAIL)); +} + +TEST_P(IPv4DatagramBasedUnboundSocketTest, IpMulticastIfSetShort) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + // Create a valid full-sized request. + ip_mreqn iface = {}; + iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); + + // Send an optlen of 1 to check that optlen is enforced. + EXPECT_THAT( + setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, 1), + SyscallFailsWithErrno(EINVAL)); +} + +TEST_P(IPv4DatagramBasedUnboundSocketTest, IpMulticastIfDefault) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + in_addr get = {}; + socklen_t size = sizeof(get); + ASSERT_THAT( + getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), + SyscallSucceeds()); + EXPECT_EQ(size, sizeof(get)); + EXPECT_EQ(get.s_addr, 0); +} + +TEST_P(IPv4DatagramBasedUnboundSocketTest, IpMulticastIfDefaultReqn) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + ip_mreqn get = {}; + socklen_t size = sizeof(get); + ASSERT_THAT( + getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), + SyscallSucceeds()); + + // getsockopt(IP_MULTICAST_IF) can only return an in_addr, so it treats the + // first sizeof(struct in_addr) bytes of struct ip_mreqn as a struct in_addr. + // Conveniently, this corresponds to the field ip_mreqn::imr_multiaddr. + EXPECT_EQ(size, sizeof(in_addr)); + + // getsockopt(IP_MULTICAST_IF) will only return the interface address which + // hasn't been set. + EXPECT_EQ(get.imr_multiaddr.s_addr, 0); + EXPECT_EQ(get.imr_address.s_addr, 0); + EXPECT_EQ(get.imr_ifindex, 0); +} + +TEST_P(IPv4DatagramBasedUnboundSocketTest, IpMulticastIfSetAddrGetReqn) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + in_addr set = {}; + set.s_addr = htonl(INADDR_LOOPBACK); + ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, + sizeof(set)), + SyscallSucceeds()); + + ip_mreqn get = {}; + socklen_t size = sizeof(get); + ASSERT_THAT( + getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), + SyscallSucceeds()); + + // getsockopt(IP_MULTICAST_IF) can only return an in_addr, so it treats the + // first sizeof(struct in_addr) bytes of struct ip_mreqn as a struct in_addr. + // Conveniently, this corresponds to the field ip_mreqn::imr_multiaddr. + EXPECT_EQ(size, sizeof(in_addr)); + EXPECT_EQ(get.imr_multiaddr.s_addr, set.s_addr); + EXPECT_EQ(get.imr_address.s_addr, 0); + EXPECT_EQ(get.imr_ifindex, 0); +} + +TEST_P(IPv4DatagramBasedUnboundSocketTest, IpMulticastIfSetReqAddrGetReqn) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + ip_mreq set = {}; + set.imr_interface.s_addr = htonl(INADDR_LOOPBACK); + ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, + sizeof(set)), + SyscallSucceeds()); + + ip_mreqn get = {}; + socklen_t size = sizeof(get); + ASSERT_THAT( + getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), + SyscallSucceeds()); + + // getsockopt(IP_MULTICAST_IF) can only return an in_addr, so it treats the + // first sizeof(struct in_addr) bytes of struct ip_mreqn as a struct in_addr. + // Conveniently, this corresponds to the field ip_mreqn::imr_multiaddr. + EXPECT_EQ(size, sizeof(in_addr)); + EXPECT_EQ(get.imr_multiaddr.s_addr, set.imr_interface.s_addr); + EXPECT_EQ(get.imr_address.s_addr, 0); + EXPECT_EQ(get.imr_ifindex, 0); +} + +TEST_P(IPv4DatagramBasedUnboundSocketTest, IpMulticastIfSetNicGetReqn) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + ip_mreqn set = {}; + set.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); + ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, + sizeof(set)), + SyscallSucceeds()); + + ip_mreqn get = {}; + socklen_t size = sizeof(get); + ASSERT_THAT( + getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), + SyscallSucceeds()); + EXPECT_EQ(size, sizeof(in_addr)); + EXPECT_EQ(get.imr_multiaddr.s_addr, 0); + EXPECT_EQ(get.imr_address.s_addr, 0); + EXPECT_EQ(get.imr_ifindex, 0); +} + +TEST_P(IPv4DatagramBasedUnboundSocketTest, IpMulticastIfSetAddr) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + in_addr set = {}; + set.s_addr = htonl(INADDR_LOOPBACK); + ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, + sizeof(set)), + SyscallSucceeds()); + + in_addr get = {}; + socklen_t size = sizeof(get); + ASSERT_THAT( + getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), + SyscallSucceeds()); + + EXPECT_EQ(size, sizeof(get)); + EXPECT_EQ(get.s_addr, set.s_addr); +} + +TEST_P(IPv4DatagramBasedUnboundSocketTest, IpMulticastIfSetReqAddr) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + ip_mreq set = {}; + set.imr_interface.s_addr = htonl(INADDR_LOOPBACK); + ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, + sizeof(set)), + SyscallSucceeds()); + + in_addr get = {}; + socklen_t size = sizeof(get); + ASSERT_THAT( + getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), + SyscallSucceeds()); + + EXPECT_EQ(size, sizeof(get)); + EXPECT_EQ(get.s_addr, set.imr_interface.s_addr); +} + +TEST_P(IPv4DatagramBasedUnboundSocketTest, IpMulticastIfSetNic) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + ip_mreqn set = {}; + set.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); + ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, + sizeof(set)), + SyscallSucceeds()); + + in_addr get = {}; + socklen_t size = sizeof(get); + ASSERT_THAT( + getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), + SyscallSucceeds()); + EXPECT_EQ(size, sizeof(get)); + EXPECT_EQ(get.s_addr, 0); +} + +TEST_P(IPv4DatagramBasedUnboundSocketTest, TestJoinGroupNoIf) { + // TODO(b/185517803): Fix for native test. + SKIP_IF(!IsRunningOnGvisor()); + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + ip_mreqn group = {}; + group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); + EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, + sizeof(group)), + SyscallFailsWithErrno(ENODEV)); +} + +TEST_P(IPv4DatagramBasedUnboundSocketTest, TestJoinGroupInvalidIf) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + ip_mreqn group = {}; + group.imr_address.s_addr = inet_addr("255.255.255"); + group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); + EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, + sizeof(group)), + SyscallFailsWithErrno(ENODEV)); +} + +// Check that multiple memberships are not allowed on the same socket. +TEST_P(IPv4DatagramBasedUnboundSocketTest, TestMultipleJoinsOnSingleSocket) { + auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto fd = socket1->get(); + ip_mreqn group = {}; + group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); + + EXPECT_THAT( + setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), + SyscallSucceeds()); + + EXPECT_THAT( + setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), + SyscallFailsWithErrno(EADDRINUSE)); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_datagram_based_socket_unbound.h b/test/syscalls/linux/socket_ipv4_datagram_based_socket_unbound.h new file mode 100644 index 000000000..7ab235cad --- /dev/null +++ b/test/syscalls/linux/socket_ipv4_datagram_based_socket_unbound.h @@ -0,0 +1,31 @@ +// Copyright 2019 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. + +#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_DATAGRAM_BASED_SOCKET_UNBOUND_H_ +#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_DATAGRAM_BASED_SOCKET_UNBOUND_H_ + +#include "test/util/socket_util.h" + +namespace gvisor { +namespace testing { + +// Test fixture for tests that apply to IPv4 datagram-based sockets. +class IPv4DatagramBasedUnboundSocketTest : public SimpleSocketTest { + void SetUp() override; +}; + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_DATAGRAM_BASED_SOCKET_UNBOUND_H_ diff --git a/test/syscalls/linux/socket_ipv4_datagram_based_socket_unbound_loopback.cc b/test/syscalls/linux/socket_ipv4_datagram_based_socket_unbound_loopback.cc new file mode 100644 index 000000000..4ab4ef261 --- /dev/null +++ b/test/syscalls/linux/socket_ipv4_datagram_based_socket_unbound_loopback.cc @@ -0,0 +1,28 @@ +// Copyright 2018 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/ip_socket_test_util.h" +#include "test/syscalls/linux/socket_ipv4_datagram_based_socket_unbound.h" + +namespace gvisor { +namespace testing { + +INSTANTIATE_TEST_SUITE_P( + IPv4Sockets, IPv4DatagramBasedUnboundSocketTest, + ::testing::ValuesIn(ApplyVecToVec<SocketKind>( + {IPv4UDPUnboundSocket, IPv4RawUDPUnboundSocket, ICMPUnboundSocket}, + AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})))); + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc index 816d1181c..05773a29c 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc @@ -15,8 +15,6 @@ #include "test/syscalls/linux/socket_ipv4_udp_unbound.h" #include <arpa/inet.h> -#include <net/if.h> -#include <sys/ioctl.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/un.h> @@ -26,10 +24,6 @@ #include "gtest/gtest.h" #include "absl/memory/memory.h" #include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/util/posix_error.h" -#include "test/util/save_util.h" -#include "test/util/socket_util.h" -#include "test/util/test_util.h" namespace gvisor { namespace testing { @@ -140,7 +134,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackNicNoDefaultSendIf) { // Register to receive multicast packets. ip_mreqn group = {}; group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); EXPECT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), SyscallSucceeds()); @@ -238,7 +232,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackNic) { // Register to receive multicast packets. ip_mreqn group = {}; group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); ASSERT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), SyscallSucceeds()); @@ -326,7 +320,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNic) { // Set the default send interface. ip_mreqn iface = {}; - iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, sizeof(iface)), SyscallSucceeds()); @@ -346,7 +340,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNic) { // Register to receive multicast packets. ip_mreqn group = {}; group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); ASSERT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), SyscallSucceeds()); @@ -437,7 +431,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicConnect) { // Set the default send interface. ip_mreqn iface = {}; - iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, sizeof(iface)), SyscallSucceeds()); @@ -457,7 +451,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicConnect) { // Register to receive multicast packets. ip_mreqn group = {}; group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); ASSERT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), SyscallSucceeds()); @@ -548,7 +542,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelf) { // Set the default send interface. ip_mreqn iface = {}; - iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, sizeof(iface)), SyscallSucceeds()); @@ -568,7 +562,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelf) { // Register to receive multicast packets. ip_mreqn group = {}; group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), SyscallSucceeds()); @@ -657,7 +651,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfConnect) { // Set the default send interface. ip_mreqn iface = {}; - iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, sizeof(iface)), SyscallSucceeds()); @@ -677,7 +671,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfConnect) { // Register to receive multicast packets. ip_mreqn group = {}; group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), SyscallSucceeds()); @@ -770,7 +764,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfNoLoop) { // Set the default send interface. ip_mreqn iface = {}; - iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, sizeof(iface)), SyscallSucceeds()); @@ -794,7 +788,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfNoLoop) { // Register to receive multicast packets. ip_mreqn group = {}; group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), SyscallSucceeds()); @@ -819,20 +813,6 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfNoLoop) { EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } -// Check that dropping a group membership that does not exist fails. -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastInvalidDrop) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Unregister from a membership that we didn't have. - ip_mreq group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_DROP_MEMBERSHIP, &group, - sizeof(group)), - SyscallFailsWithErrno(EADDRNOTAVAIL)); -} - // Check that dropping a group membership prevents multicast packets from being // delivered. Default send address configured by bind and group membership // interface configured by address. @@ -917,7 +897,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastDropNic) { // Register and unregister to receive multicast packets. ip_mreqn group = {}; group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); EXPECT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), SyscallSucceeds()); @@ -943,260 +923,6 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastDropNic) { PosixErrorIs(EAGAIN, ::testing::_)); } -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfZero) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn iface = {}; - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallSucceeds()); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfInvalidNic) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn iface = {}; - iface.imr_ifindex = -1; - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallFailsWithErrno(EADDRNOTAVAIL)); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfInvalidAddr) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreq iface = {}; - iface.imr_interface.s_addr = inet_addr("255.255.255"); - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, - sizeof(iface)), - SyscallFailsWithErrno(EADDRNOTAVAIL)); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfSetShort) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - // Create a valid full-sized request. - ip_mreqn iface = {}; - iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - - // Send an optlen of 1 to check that optlen is enforced. - EXPECT_THAT( - setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, 1), - SyscallFailsWithErrno(EINVAL)); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfDefault) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - in_addr get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - EXPECT_EQ(size, sizeof(get)); - EXPECT_EQ(get.s_addr, 0); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfDefaultReqn) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - - // getsockopt(IP_MULTICAST_IF) can only return an in_addr, so it treats the - // first sizeof(struct in_addr) bytes of struct ip_mreqn as a struct in_addr. - // Conveniently, this corresponds to the field ip_mreqn::imr_multiaddr. - EXPECT_EQ(size, sizeof(in_addr)); - - // getsockopt(IP_MULTICAST_IF) will only return the interface address which - // hasn't been set. - EXPECT_EQ(get.imr_multiaddr.s_addr, 0); - EXPECT_EQ(get.imr_address.s_addr, 0); - EXPECT_EQ(get.imr_ifindex, 0); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfSetAddrGetReqn) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - in_addr set = {}; - set.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, - sizeof(set)), - SyscallSucceeds()); - - ip_mreqn get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - - // getsockopt(IP_MULTICAST_IF) can only return an in_addr, so it treats the - // first sizeof(struct in_addr) bytes of struct ip_mreqn as a struct in_addr. - // Conveniently, this corresponds to the field ip_mreqn::imr_multiaddr. - EXPECT_EQ(size, sizeof(in_addr)); - EXPECT_EQ(get.imr_multiaddr.s_addr, set.s_addr); - EXPECT_EQ(get.imr_address.s_addr, 0); - EXPECT_EQ(get.imr_ifindex, 0); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfSetReqAddrGetReqn) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreq set = {}; - set.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, - sizeof(set)), - SyscallSucceeds()); - - ip_mreqn get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - - // getsockopt(IP_MULTICAST_IF) can only return an in_addr, so it treats the - // first sizeof(struct in_addr) bytes of struct ip_mreqn as a struct in_addr. - // Conveniently, this corresponds to the field ip_mreqn::imr_multiaddr. - EXPECT_EQ(size, sizeof(in_addr)); - EXPECT_EQ(get.imr_multiaddr.s_addr, set.imr_interface.s_addr); - EXPECT_EQ(get.imr_address.s_addr, 0); - EXPECT_EQ(get.imr_ifindex, 0); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfSetNicGetReqn) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn set = {}; - set.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, - sizeof(set)), - SyscallSucceeds()); - - ip_mreqn get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - EXPECT_EQ(size, sizeof(in_addr)); - EXPECT_EQ(get.imr_multiaddr.s_addr, 0); - EXPECT_EQ(get.imr_address.s_addr, 0); - EXPECT_EQ(get.imr_ifindex, 0); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfSetAddr) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - in_addr set = {}; - set.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, - sizeof(set)), - SyscallSucceeds()); - - in_addr get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - - EXPECT_EQ(size, sizeof(get)); - EXPECT_EQ(get.s_addr, set.s_addr); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfSetReqAddr) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreq set = {}; - set.imr_interface.s_addr = htonl(INADDR_LOOPBACK); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, - sizeof(set)), - SyscallSucceeds()); - - in_addr get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - - EXPECT_EQ(size, sizeof(get)); - EXPECT_EQ(get.s_addr, set.imr_interface.s_addr); -} - -TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIfSetNic) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn set = {}; - set.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - ASSERT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &set, - sizeof(set)), - SyscallSucceeds()); - - in_addr get = {}; - socklen_t size = sizeof(get); - ASSERT_THAT( - getsockopt(socket1->get(), IPPROTO_IP, IP_MULTICAST_IF, &get, &size), - SyscallSucceeds()); - EXPECT_EQ(size, sizeof(get)); - EXPECT_EQ(get.s_addr, 0); -} - -TEST_P(IPv4UDPUnboundSocketTest, TestJoinGroupNoIf) { - // TODO(b/185517803): Fix for native test. - SKIP_IF(!IsRunningOnGvisor()); - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallFailsWithErrno(ENODEV)); -} - -TEST_P(IPv4UDPUnboundSocketTest, TestJoinGroupInvalidIf) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - - ip_mreqn group = {}; - group.imr_address.s_addr = inet_addr("255.255.255"); - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, - sizeof(group)), - SyscallFailsWithErrno(ENODEV)); -} - -// Check that multiple memberships are not allowed on the same socket. -TEST_P(IPv4UDPUnboundSocketTest, TestMultipleJoinsOnSingleSocket) { - auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); - auto fd = socket1->get(); - ip_mreqn group = {}; - group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); - - EXPECT_THAT( - setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), - SyscallSucceeds()); - - EXPECT_THAT( - setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), - SyscallFailsWithErrno(EADDRINUSE)); -} - // Check that two sockets can join the same multicast group at the same time. TEST_P(IPv4UDPUnboundSocketTest, TestTwoSocketsJoinSameMulticastGroup) { auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); @@ -1204,7 +930,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestTwoSocketsJoinSameMulticastGroup) { ip_mreqn group = {}; group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); EXPECT_THAT(setsockopt(socket1->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), SyscallSucceeds()); @@ -1419,7 +1145,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenJoinThenReceive) { // Register to receive multicast packets. ip_mreqn group = {}; group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); ASSERT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), SyscallSucceeds()); @@ -2110,16 +1836,11 @@ TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPPKTINFO) { EXPECT_EQ(cmsg->cmsg_level, level); EXPECT_EQ(cmsg->cmsg_type, type); - // Get loopback index. - ifreq ifr = {}; - absl::SNPrintF(ifr.ifr_name, IFNAMSIZ, "lo"); - ASSERT_THAT(ioctl(sender->get(), SIOCGIFINDEX, &ifr), SyscallSucceeds()); - ASSERT_NE(ifr.ifr_ifindex, 0); - // Check the data in_pktinfo received_pktinfo = {}; memcpy(&received_pktinfo, CMSG_DATA(cmsg), sizeof(in_pktinfo)); - EXPECT_EQ(received_pktinfo.ipi_ifindex, ifr.ifr_ifindex); + EXPECT_EQ(received_pktinfo.ipi_ifindex, + ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())); EXPECT_EQ(received_pktinfo.ipi_spec_dst.s_addr, htonl(INADDR_LOOPBACK)); EXPECT_EQ(received_pktinfo.ipi_addr.s_addr, htonl(INADDR_LOOPBACK)); } @@ -2439,7 +2160,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIPPacketInfo) { // Register to receive multicast packets. ip_mreqn group = {}; group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); - group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()); ASSERT_THAT(setsockopt(receiver_socket->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)), SyscallSucceeds()); @@ -2484,16 +2205,10 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastIPPacketInfo) { EXPECT_EQ(cmsg->cmsg_level, IPPROTO_IP); EXPECT_EQ(cmsg->cmsg_type, IP_PKTINFO); - // Get loopback index. - ifreq ifr = {}; - absl::SNPrintF(ifr.ifr_name, IFNAMSIZ, "lo"); - ASSERT_THAT(ioctl(receiver_socket->get(), SIOCGIFINDEX, &ifr), - SyscallSucceeds()); - ASSERT_NE(ifr.ifr_ifindex, 0); - in_pktinfo received_pktinfo = {}; memcpy(&received_pktinfo, CMSG_DATA(cmsg), sizeof(in_pktinfo)); - EXPECT_EQ(received_pktinfo.ipi_ifindex, ifr.ifr_ifindex); + EXPECT_EQ(received_pktinfo.ipi_ifindex, + ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())); if (IsRunningOnGvisor()) { // This should actually be a unicast address assigned to the interface. // 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_ipv4_udp_unbound_loopback.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_loopback.cc index 00930c544..bef284530 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_loopback.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_loopback.cc @@ -12,12 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <vector> - #include "test/syscalls/linux/ip_socket_test_util.h" #include "test/syscalls/linux/socket_ipv4_udp_unbound.h" -#include "test/util/socket_util.h" -#include "test/util/test_util.h" namespace gvisor { namespace testing { diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound.cc b/test/syscalls/linux/socket_ipv6_udp_unbound.cc index 612fd531c..c431bf771 100644 --- a/test/syscalls/linux/socket_ipv6_udp_unbound.cc +++ b/test/syscalls/linux/socket_ipv6_udp_unbound.cc @@ -39,6 +39,86 @@ namespace gvisor { namespace testing { +using ::testing::IsNull; +using ::testing::NotNull; + +TEST_P(IPv6UDPUnboundSocketTest, IPv6PacketInfo) { + auto sender_socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver_socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + auto sender_addr = V6Loopback(); + ASSERT_THAT(bind(sender_socket->get(), AsSockAddr(&sender_addr.addr), + sender_addr.addr_len), + SyscallSucceeds()); + + auto receiver_addr = V6Loopback(); + ASSERT_THAT(bind(receiver_socket->get(), AsSockAddr(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + socklen_t receiver_addr_len = receiver_addr.addr_len; + ASSERT_THAT(getsockname(receiver_socket->get(), + AsSockAddr(&receiver_addr.addr), &receiver_addr_len), + SyscallSucceeds()); + ASSERT_EQ(receiver_addr_len, receiver_addr.addr_len); + + // Make sure we get IPv6 packet information as control messages. + constexpr int one = 1; + ASSERT_THAT(setsockopt(receiver_socket->get(), IPPROTO_IPV6, IPV6_RECVPKTINFO, + &one, sizeof(one)), + SyscallSucceeds()); + + // Send a packet - we don't care about the packet itself, only the returned + // IPV6_PKTINFO control message. + char send_buf[200]; + RandomizeBuffer(send_buf, sizeof(send_buf)); + ASSERT_THAT(RetryEINTR(sendto)( + sender_socket->get(), send_buf, sizeof(send_buf), 0, + AsSockAddr(&receiver_addr.addr), receiver_addr.addr_len), + SyscallSucceedsWithValue(sizeof(send_buf))); + + // Check that we received the packet with the packet information control + // message. + char recv_buf[sizeof(send_buf) + 1]; + in6_pktinfo received_pktinfo; + char recv_cmsg_buf[CMSG_SPACE(sizeof(received_pktinfo))]; + iovec recv_iov = { + .iov_base = recv_buf, + .iov_len = sizeof(recv_buf), + }; + msghdr recv_msg = { + .msg_iov = &recv_iov, + .msg_iovlen = 1, + .msg_control = recv_cmsg_buf, + .msg_controllen = sizeof(recv_cmsg_buf), + }; + ASSERT_THAT( + RetryEINTR(recvmsg)(receiver_socket->get(), &recv_msg, 0 /* flags */), + 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(in6_pktinfo))); + EXPECT_EQ(cmsg->cmsg_level, IPPROTO_IPV6); + EXPECT_EQ(cmsg->cmsg_type, IPV6_PKTINFO); + // As per cmsg(3) (https://www.man7.org/linux/man-pages/man3/cmsg.3.html), + // + // CMSG_DATA() returns a pointer to the data portion of a cmsghdr. The + // pointer returned cannot be assumed to be suitably aligned for accessing + // arbitrary payload data types. Applications should not cast it to a + // pointer type matching the payload, but should instead use memcpy(3) to + // copy data to or from a suitably declared object. + memcpy(&received_pktinfo, CMSG_DATA(cmsg), sizeof(received_pktinfo)); + EXPECT_EQ( + memcmp(&received_pktinfo.ipi6_addr, + &(reinterpret_cast<sockaddr_in6*>(&sender_addr.addr)->sin6_addr), + sizeof(received_pktinfo.ipi6_addr)), + 0); + EXPECT_EQ(received_pktinfo.ipi6_ifindex, + ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())); + EXPECT_THAT(CMSG_NXTHDR(&recv_msg, cmsg), IsNull()); +} + // Test that socket will receive IP_RECVORIGDSTADDR control message. TEST_P(IPv6UDPUnboundSocketTest, SetAndReceiveIPReceiveOrigDstAddr) { auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); 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 09f070797..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()); @@ -39,7 +37,7 @@ TEST_P(IPv6UDPUnboundExternalNetworkingSocketTest, TestJoinLeaveMulticast) { .ipv6mr_multiaddr = reinterpret_cast<sockaddr_in6*>(&multicast_addr.addr)->sin6_addr, .ipv6mr_interface = static_cast<decltype(ipv6_mreq::ipv6mr_interface)>( - ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"))), + ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())), }; ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &group_req, sizeof(group_req)), diff --git a/test/syscalls/linux/socket_unix.cc b/test/syscalls/linux/socket_unix.cc index cf96b2075..43433eaae 100644 --- a/test/syscalls/linux/socket_unix.cc +++ b/test/syscalls/linux/socket_unix.cc @@ -27,7 +27,10 @@ #include "gtest/gtest.h" #include "absl/strings/string_view.h" #include "test/syscalls/linux/unix_domain_socket_test_util.h" +#include "test/util/file_descriptor.h" +#include "test/util/memory_util.h" #include "test/util/socket_util.h" +#include "test/util/temp_path.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" @@ -268,6 +271,18 @@ TEST_P(UnixSocketPairTest, SocketReopenFromProcfs) { } } +// Repro for b/196804997. +TEST_P(UnixSocketPairTest, SendFromMmapBeyondEof) { + TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); + Mapping m = ASSERT_NO_ERRNO_AND_VALUE( + Mmap(nullptr, kPageSize, PROT_READ, MAP_SHARED, fd.get(), 0)); + + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + ASSERT_THAT(send(sockets->first_fd(), m.ptr(), m.len(), 0), + SyscallFailsWithErrno(EFAULT)); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/socket_unix_stream.cc b/test/syscalls/linux/socket_unix_stream.cc index 6e9f70f8c..2f3cfc3f3 100644 --- a/test/syscalls/linux/socket_unix_stream.cc +++ b/test/syscalls/linux/socket_unix_stream.cc @@ -181,6 +181,21 @@ TEST_P(StreamUnixSocketPairTest, SetSocketSendBuf) { ASSERT_EQ(quarter_sz, val); } +TEST_P(StreamUnixSocketPairTest, SendBufferOverflow) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + auto s = sockets->first_fd(); + + constexpr int kBufSz = 4096; + std::vector<char> buf(kBufSz * 4); + ASSERT_THAT(RetryEINTR(send)(s, buf.data(), buf.size(), MSG_DONTWAIT), + SyscallSucceeds()); + // The new buffer size should be smaller that the amount of data in the queue. + ASSERT_THAT(setsockopt(s, SOL_SOCKET, SO_SNDBUF, &kBufSz, sizeof(kBufSz)), + SyscallSucceeds()); + ASSERT_THAT(RetryEINTR(send)(s, buf.data(), buf.size(), MSG_DONTWAIT), + SyscallFailsWithErrno(EAGAIN)); +} + TEST_P(StreamUnixSocketPairTest, IncreasedSocketSendBufUnblocksWrites) { auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); int sock = sockets->first_fd(); diff --git a/test/syscalls/linux/splice.cc b/test/syscalls/linux/splice.cc index c85f6da0b..4a10ae8d2 100644 --- a/test/syscalls/linux/splice.cc +++ b/test/syscalls/linux/splice.cc @@ -195,81 +195,6 @@ TEST(SpliceTest, PipeOffsets) { SyscallFailsWithErrno(ESPIPE)); } -// Event FDs may be used with splice without an offset. -TEST(SpliceTest, FromEventFD) { - // Open the input eventfd with an initial value so that it is readable. - constexpr uint64_t kEventFDValue = 1; - int efd; - ASSERT_THAT(efd = eventfd(kEventFDValue, 0), SyscallSucceeds()); - const FileDescriptor in_fd(efd); - - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Splice 8-byte eventfd value to pipe. - constexpr int kEventFDSize = 8; - EXPECT_THAT(splice(in_fd.get(), nullptr, wfd.get(), nullptr, kEventFDSize, 0), - SyscallSucceedsWithValue(kEventFDSize)); - - // Contents should be equal. - std::vector<char> rbuf(kEventFDSize); - ASSERT_THAT(read(rfd.get(), rbuf.data(), rbuf.size()), - SyscallSucceedsWithValue(kEventFDSize)); - EXPECT_EQ(memcmp(rbuf.data(), &kEventFDValue, rbuf.size()), 0); -} - -// Event FDs may not be used with splice with an offset. -TEST(SpliceTest, FromEventFDOffset) { - int efd; - ASSERT_THAT(efd = eventfd(0, 0), SyscallSucceeds()); - const FileDescriptor in_fd(efd); - - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Attempt to splice 8-byte eventfd value to pipe with offset. - // - // This is not allowed because eventfd doesn't support pread. - constexpr int kEventFDSize = 8; - loff_t in_off = 0; - EXPECT_THAT(splice(in_fd.get(), &in_off, wfd.get(), nullptr, kEventFDSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - -// Event FDs may not be used with splice with an offset. -TEST(SpliceTest, ToEventFDOffset) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - const FileDescriptor wfd(fds[1]); - - // Fill with a value. - constexpr int kEventFDSize = 8; - std::vector<char> buf(kEventFDSize); - buf[0] = 1; - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kEventFDSize)); - - int efd; - ASSERT_THAT(efd = eventfd(0, 0), SyscallSucceeds()); - const FileDescriptor out_fd(efd); - - // Attempt to splice 8-byte eventfd value to pipe with offset. - // - // This is not allowed because eventfd doesn't support pwrite. - loff_t out_off = 0; - EXPECT_THAT( - splice(rfd.get(), nullptr, out_fd.get(), &out_off, kEventFDSize, 0), - SyscallFailsWithErrno(EINVAL)); -} - TEST(SpliceTest, ToPipe) { // Open the input file. const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); @@ -852,34 +777,6 @@ TEST(SpliceTest, FromPipeMaxFileSize) { EXPECT_EQ(memcmp(rbuf.data(), buf.data(), buf.size()), 0); } -TEST(SpliceTest, FromPipeToDevZero) { - // Create a new pipe. - int fds[2]; - ASSERT_THAT(pipe(fds), SyscallSucceeds()); - const FileDescriptor rfd(fds[0]); - FileDescriptor wfd(fds[1]); - - // Fill with some random data. - std::vector<char> buf(kPageSize); - RandomizeBuffer(buf.data(), buf.size()); - ASSERT_THAT(write(wfd.get(), buf.data(), buf.size()), - SyscallSucceedsWithValue(kPageSize)); - - const FileDescriptor zero = - ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/zero", O_WRONLY)); - - // Close the write end to prevent blocking below. - wfd.reset(); - - // Splice to /dev/zero. The first call should empty the pipe, and the return - // value should not exceed the number of bytes available for reading. - EXPECT_THAT( - splice(rfd.get(), nullptr, zero.get(), nullptr, kPageSize + 123, 0), - SyscallSucceedsWithValue(kPageSize)); - EXPECT_THAT(splice(rfd.get(), nullptr, zero.get(), nullptr, 1, 0), - SyscallSucceedsWithValue(0)); -} - static volatile int signaled = 0; void SigUsr1Handler(int sig, siginfo_t* info, void* context) { signaled = 1; } diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc index cb77986c2..bbcb7e4fd 100644 --- a/test/syscalls/linux/tcp_socket.cc +++ b/test/syscalls/linux/tcp_socket.cc @@ -2067,6 +2067,69 @@ TEST_P(SimpleTcpSocketTest, GetSocketAcceptConnWithShutdown) { EXPECT_EQ(got, 0); } +void ShutdownConnectingSocket(int domain, int shutdown_mode) { + FileDescriptor bound_s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(domain, SOCK_STREAM, IPPROTO_TCP)); + + sockaddr_storage bound_addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(domain)); + socklen_t bound_addrlen = sizeof(bound_addr); + + ASSERT_THAT(bind(bound_s.get(), AsSockAddr(&bound_addr), bound_addrlen), + SyscallSucceeds()); + + // Start listening. Use a zero backlog to only allow one connection in the + // accept queue. + ASSERT_THAT(listen(bound_s.get(), 0), SyscallSucceeds()); + + // Get the addresses the socket is bound to because the port is chosen by the + // stack. + ASSERT_THAT( + getsockname(bound_s.get(), AsSockAddr(&bound_addr), &bound_addrlen), + SyscallSucceeds()); + + // Establish a connection. But do not accept it. That way, subsequent + // connections will not get a SYN-ACK because the queue is full. + FileDescriptor connected_s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(domain, SOCK_STREAM, IPPROTO_TCP)); + ASSERT_THAT(connect(connected_s.get(), + reinterpret_cast<const struct sockaddr*>(&bound_addr), + bound_addrlen), + SyscallSucceeds()); + + FileDescriptor connecting_s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(domain, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); + ASSERT_THAT(connect(connecting_s.get(), + reinterpret_cast<const struct sockaddr*>(&bound_addr), + bound_addrlen), + SyscallFailsWithErrno(EINPROGRESS)); + + // Now the test: when a connecting socket is shutdown, the socket should enter + // an error state. + EXPECT_THAT(shutdown(connecting_s.get(), shutdown_mode), SyscallSucceeds()); + + // We don't need to specify any events to get POLLHUP or POLLERR because these + // are always tracked. + struct pollfd poll_fd = { + .fd = connecting_s.get(), + }; + + EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 0), SyscallSucceedsWithValue(1)); + EXPECT_EQ(poll_fd.revents, POLLHUP | POLLERR); +} + +TEST_P(SimpleTcpSocketTest, ShutdownReadConnectingSocket) { + ShutdownConnectingSocket(GetParam(), SHUT_RD); +} + +TEST_P(SimpleTcpSocketTest, ShutdownWriteConnectingSocket) { + ShutdownConnectingSocket(GetParam(), SHUT_WR); +} + +TEST_P(SimpleTcpSocketTest, ShutdownReadWriteConnectingSocket) { + ShutdownConnectingSocket(GetParam(), SHUT_RDWR); +} + // Tests that connecting to an unspecified address results in ECONNREFUSED. TEST_P(SimpleTcpSocketTest, ConnectUnspecifiedAddress) { sockaddr_storage addr; @@ -2088,6 +2151,126 @@ TEST_P(SimpleTcpSocketTest, ConnectUnspecifiedAddress) { } } +TEST_P(SimpleTcpSocketTest, OnlyAcknowledgeBacklogConnections) { + // At some point, there was a bug in gVisor where a connection could be + // SYN-ACK'd by the server even if the accept queue was already full. This was + // possible because once the listener would process an ACK, it would move the + // new connection in the accept queue asynchronously. It created an + // opportunity where the listener could process another SYN before completing + // the delivery that would have filled the accept queue. + // + // This test checks that there is no such race. + + std::array<std::optional<ScopedThread>, 100> threads; + for (auto& thread : threads) { + thread.emplace([]() { + FileDescriptor bound_s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + + sockaddr_storage bound_addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); + socklen_t bound_addrlen = sizeof(bound_addr); + + ASSERT_THAT(bind(bound_s.get(), AsSockAddr(&bound_addr), bound_addrlen), + SyscallSucceeds()); + + // Start listening. Use a zero backlog to only allow one connection in the + // accept queue. + ASSERT_THAT(listen(bound_s.get(), 0), SyscallSucceeds()); + + // Get the addresses the socket is bound to because the port is chosen by + // the stack. + ASSERT_THAT( + getsockname(bound_s.get(), AsSockAddr(&bound_addr), &bound_addrlen), + SyscallSucceeds()); + + // Establish a connection, but do not accept it. + FileDescriptor connected_s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + ASSERT_THAT(connect(connected_s.get(), + reinterpret_cast<const struct sockaddr*>(&bound_addr), + bound_addrlen), + SyscallSucceeds()); + + // Immediately attempt to establish another connection. Use non blocking + // socket because this is expected to timeout. + FileDescriptor connecting_s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(GetParam(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); + ASSERT_THAT(connect(connecting_s.get(), + reinterpret_cast<const struct sockaddr*>(&bound_addr), + bound_addrlen), + SyscallFailsWithErrno(EINPROGRESS)); + + struct pollfd poll_fd = { + .fd = connecting_s.get(), + .events = POLLOUT, + }; + EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10), + SyscallSucceedsWithValue(0)); + }); + } +} + +// Tests that send will return EWOULDBLOCK initially with large buffer and will +// succeed after the send buffer size is increased. +TEST_P(TcpSocketTest, SendUnblocksOnSendBufferIncrease) { + // Set the FD to O_NONBLOCK. + int opts; + ASSERT_THAT(opts = fcntl(first_fd, F_GETFL), SyscallSucceeds()); + opts |= O_NONBLOCK; + ASSERT_THAT(fcntl(first_fd, F_SETFL, opts), SyscallSucceeds()); + + // Get maximum buffer size by trying to set it to a large value. + constexpr int kSndBufSz = 0xffffffff; + ASSERT_THAT(setsockopt(first_fd, SOL_SOCKET, SO_SNDBUF, &kSndBufSz, + sizeof(kSndBufSz)), + SyscallSucceeds()); + + int max_buffer_sz = 0; + socklen_t max_len = sizeof(max_buffer_sz); + ASSERT_THAT( + getsockopt(first_fd, SOL_SOCKET, SO_SNDBUF, &max_buffer_sz, &max_len), + SyscallSucceeds()); + + int buffer_sz = max_buffer_sz >> 2; + EXPECT_THAT(setsockopt(first_fd, SOL_SOCKET, SO_SNDBUF, &buffer_sz, + sizeof(buffer_sz)), + SyscallSucceedsWithValue(0)); + + // Create a large buffer that will be used for sending. + std::vector<char> buffer(max_buffer_sz); + + // Write until we receive an error. + while (RetryEINTR(send)(first_fd, buffer.data(), buffer.size(), 0) != -1) { + // Sleep to give linux a chance to move data from the send buffer to the + // receive buffer. + usleep(10000); // 10ms. + } + + // The last error should have been EWOULDBLOCK. + ASSERT_EQ(errno, EWOULDBLOCK); + + ScopedThread send_thread([this]() { + int flags = 0; + ASSERT_THAT(flags = fcntl(first_fd, F_GETFL), SyscallSucceeds()); + EXPECT_THAT(fcntl(first_fd, F_SETFL, flags & ~O_NONBLOCK), + SyscallSucceeds()); + + // Expect the send() to succeed. + char buffer; + ASSERT_THAT(RetryEINTR(send)(first_fd, &buffer, sizeof(buffer), 0), + SyscallSucceeds()); + }); + + // Set SO_SNDBUF to maximum buffer size allowed. + buffer_sz = max_buffer_sz >> 1; + EXPECT_THAT(setsockopt(first_fd, SOL_SOCKET, SO_SNDBUF, &buffer_sz, + sizeof(buffer_sz)), + SyscallSucceedsWithValue(0)); + + send_thread.Join(); +} + INSTANTIATE_TEST_SUITE_P(AllInetTests, SimpleTcpSocketTest, ::testing::Values(AF_INET, AF_INET6)); diff --git a/test/syscalls/linux/tuntap.cc b/test/syscalls/linux/tuntap.cc index 7c9c5c870..956208bee 100644 --- a/test/syscalls/linux/tuntap.cc +++ b/test/syscalls/linux/tuntap.cc @@ -73,29 +73,14 @@ PosixErrorOr<Link> GetLinkByName(const std::string& name) { return PosixError(ENOENT, "interface not found"); } -struct pihdr { - uint16_t pi_flags; - uint16_t pi_protocol; -} __attribute__((packed)); - -struct ping_pkt { - pihdr pi; - struct ethhdr eth; - struct iphdr ip; - struct icmphdr icmp; +struct ping_ip_pkt { + iphdr ip; + icmphdr icmp; char payload[64]; } __attribute__((packed)); -ping_pkt CreatePingPacket(const uint8_t srcmac[ETH_ALEN], const in_addr_t srcip, - const uint8_t dstmac[ETH_ALEN], - const in_addr_t dstip) { - ping_pkt pkt = {}; - - pkt.pi.pi_protocol = htons(ETH_P_IP); - - memcpy(pkt.eth.h_dest, dstmac, sizeof(pkt.eth.h_dest)); - memcpy(pkt.eth.h_source, srcmac, sizeof(pkt.eth.h_source)); - pkt.eth.h_proto = htons(ETH_P_IP); +ping_ip_pkt CreatePingIPPacket(const in_addr_t srcip, const in_addr_t dstip) { + ping_ip_pkt pkt = {}; pkt.ip.ihl = 5; pkt.ip.version = 4; @@ -122,6 +107,33 @@ ping_pkt CreatePingPacket(const uint8_t srcmac[ETH_ALEN], const in_addr_t srcip, return pkt; } +struct pihdr { + uint16_t pi_flags; + uint16_t pi_protocol; +} __attribute__((packed)); + +struct ping_pkt { + pihdr pi; + ethhdr eth; + ping_ip_pkt ip_pkt; +} __attribute__((packed)); + +ping_pkt CreatePingPacket(const uint8_t srcmac[ETH_ALEN], const in_addr_t srcip, + const uint8_t dstmac[ETH_ALEN], + const in_addr_t dstip) { + ping_pkt pkt = {}; + + pkt.pi.pi_protocol = htons(ETH_P_IP); + + memcpy(pkt.eth.h_dest, dstmac, sizeof(pkt.eth.h_dest)); + memcpy(pkt.eth.h_source, srcmac, sizeof(pkt.eth.h_source)); + pkt.eth.h_proto = htons(ETH_P_IP); + + pkt.ip_pkt = CreatePingIPPacket(srcip, dstip); + + return pkt; +} + struct arp_pkt { pihdr pi; struct ethhdr eth; @@ -274,13 +286,26 @@ TEST_F(TuntapTest, WriteToDownDevice) { EXPECT_THAT(write(fd.get(), buf, sizeof(buf)), SyscallFailsWithErrno(EIO)); } -PosixErrorOr<FileDescriptor> OpenAndAttachTap(const std::string& dev_name, - const in_addr_t dev_addr) { +struct TunTapInterface { + FileDescriptor fd; + Link link; +}; + +PosixErrorOr<TunTapInterface> OpenAndAttachTunTap(const std::string& dev_name, + const in_addr_t dev_addr, + bool tap, bool no_pi) { // Interface creation. ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, Open(kDevNetTun, O_RDWR)); struct ifreq ifr_set = {}; - ifr_set.ifr_flags = IFF_TAP; + if (tap) { + ifr_set.ifr_flags |= IFF_TAP; + } else { + ifr_set.ifr_flags |= IFF_TUN; + } + if (no_pi) { + ifr_set.ifr_flags |= IFF_NO_PI; + } strncpy(ifr_set.ifr_name, dev_name.c_str(), IFNAMSIZ); if (ioctl(fd.get(), TUNSETIFF, &ifr_set) < 0) { return PosixError(errno); @@ -296,13 +321,15 @@ PosixErrorOr<FileDescriptor> OpenAndAttachTap(const std::string& dev_name, if (!IsRunningOnGvisor()) { // FIXME(b/110961832): gVisor doesn't support setting MAC address on // interfaces yet. - RETURN_IF_ERRNO(LinkSetMacAddr(link.index, kMacA, sizeof(kMacA))); + if (tap) { + RETURN_IF_ERRNO(LinkSetMacAddr(link.index, kMacA, sizeof(kMacA))); + } // FIXME(b/110961832): gVisor always creates enabled/up'd interfaces. RETURN_IF_ERRNO(LinkChangeFlags(link.index, IFF_UP, IFF_UP)); } - return fd; + return TunTapInterface{.fd = std::move(fd), .link = std::move(link)}; } // This test sets up a TAP device and pings kernel by sending ICMP echo request. @@ -322,8 +349,9 @@ PosixErrorOr<FileDescriptor> OpenAndAttachTap(const std::string& dev_name, TEST_F(TuntapTest, PingKernel) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTap(kTapName, kTapIPAddr)); + const auto& [fd, link] = ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTunTap( + kTapName, kTapIPAddr, true /* tap */, false /* no_pi */)); + ping_pkt ping_req = CreatePingPacket(kMacB, kTapPeerIPAddr, kMacA, kTapIPAddr); std::string arp_rep = @@ -365,10 +393,10 @@ TEST_F(TuntapTest, PingKernel) { // Process ping response packet. if (n >= sizeof(ping_pkt) && r.pi.pi_protocol == ping_req.pi.pi_protocol && - r.ping.ip.protocol == ping_req.ip.protocol && - !memcmp(&r.ping.ip.saddr, &ping_req.ip.daddr, kIPLen) && - !memcmp(&r.ping.ip.daddr, &ping_req.ip.saddr, kIPLen) && - r.ping.icmp.type == 0 && r.ping.icmp.code == 0) { + r.ping.ip_pkt.ip.protocol == ping_req.ip_pkt.ip.protocol && + !memcmp(&r.ping.ip_pkt.ip.saddr, &ping_req.ip_pkt.ip.daddr, kIPLen) && + !memcmp(&r.ping.ip_pkt.ip.daddr, &ping_req.ip_pkt.ip.saddr, kIPLen) && + r.ping.ip_pkt.icmp.type == 0 && r.ping.ip_pkt.icmp.code == 0) { // Ends and passes the test. break; } @@ -378,8 +406,8 @@ TEST_F(TuntapTest, PingKernel) { TEST_F(TuntapTest, SendUdpTriggersArpResolution) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTap(kTapName, kTapIPAddr)); + const auto& [fd, link] = ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTunTap( + kTapName, kTapIPAddr, true /* tap */, false /* no_pi */)); // Send a UDP packet to remote. int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); @@ -433,19 +461,17 @@ TEST_F(TuntapTest, TUNNoPacketInfo) { EXPECT_NO_ERRNO(LinkAddLocalAddr(link.index, AF_INET, 24, &dev_ipv4_addr, sizeof(dev_ipv4_addr))); - ping_pkt ping_req = - CreatePingPacket(kMacB, kTapPeerIPAddr, kMacA, kTapIPAddr); - size_t packet_size = sizeof(ping_req) - offsetof(ping_pkt, ip); + ping_ip_pkt ping_req = CreatePingIPPacket(kTapPeerIPAddr, kTapIPAddr); // Send ICMP query - EXPECT_THAT(write(fd.get(), &ping_req.ip, packet_size), - SyscallSucceedsWithValue(packet_size)); + EXPECT_THAT(write(fd.get(), &ping_req, sizeof(ping_req)), + SyscallSucceedsWithValue(sizeof(ping_req))); // Receive loop to process inbound packets. while (1) { - ping_pkt ping_resp = {}; - EXPECT_THAT(read(fd.get(), &ping_resp.ip, packet_size), - SyscallSucceedsWithValue(packet_size)); + ping_ip_pkt ping_resp = {}; + EXPECT_THAT(read(fd.get(), &ping_resp, sizeof(ping_req)), + SyscallSucceedsWithValue(sizeof(ping_req))); // Process ping response packet. if (!memcmp(&ping_resp.ip.saddr, &ping_req.ip.daddr, kIPLen) && @@ -465,8 +491,8 @@ TEST_F(TuntapTest, TCPBlockingConnectFailsArpResolution) { FileDescriptor sender = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTap(kTapName, kTapIPAddr)); + const auto tuntap = ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTunTap( + kTapName, kTapIPAddr, true /* tap */, false /* no_pi */)); sockaddr_in connect_addr = { .sin_family = AF_INET, @@ -487,8 +513,8 @@ TEST_F(TuntapTest, TCPNonBlockingConnectFailsArpResolution) { FileDescriptor sender = ASSERT_NO_ERRNO_AND_VALUE( Socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTap(kTapName, kTapIPAddr)); + const auto tuntap = ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTunTap( + kTapName, kTapIPAddr, true /* tap */, false /* no_pi */)); sockaddr_in connect_addr = { .sin_family = AF_INET, @@ -518,8 +544,8 @@ TEST_F(TuntapTest, TCPNonBlockingConnectFailsArpResolution) { TEST_F(TuntapTest, WriteHangBug155928773) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTap(kTapName, kTapIPAddr)); + const auto tuntap = ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTunTap( + kTapName, kTapIPAddr, true /* tap */, false /* no_pi */)); int sock = socket(AF_INET, SOCK_DGRAM, 0); ASSERT_THAT(sock, SyscallSucceeds()); @@ -534,5 +560,95 @@ TEST_F(TuntapTest, WriteHangBug155928773) { write(sock, "hello", 5); } +// Test that raw packet sockets do not need/include link headers when +// sending/receiving packets to/from pure L3 (e.g. TUN) interfaces. +TEST_F(TuntapTest, RawPacketSocket) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + auto [tun, link] = ASSERT_NO_ERRNO_AND_VALUE(OpenAndAttachTunTap( + kTunName, kTapIPAddr, false /* tap */, true /* no_pi */)); + FileDescriptor packet_sock = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP))); + + constexpr int kInfiniteTimeout = -1; + + uint8_t hardware_address_length = 0; + if (IsRunningOnGvisor()) { + // TODO(https://gvisor.dev/issue/6530): Do not assume all interfaces have + // an ethernet address. + hardware_address_length = ETH_ALEN; + } + + { + const ping_ip_pkt ping_req = CreatePingIPPacket(kTapPeerIPAddr, kTapIPAddr); + ASSERT_THAT(write(tun.get(), &ping_req, sizeof(ping_req)), + SyscallSucceedsWithValue(sizeof(ping_req))); + // Wait for the packet socket to become readable. + pollfd pfd = { + .fd = packet_sock.get(), + .events = POLLIN, + }; + ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kInfiniteTimeout), + SyscallSucceedsWithValue(1)); + + char read_buf[sizeof(ping_req) + 1]; + struct sockaddr_ll src; + socklen_t src_len = sizeof(src); + ASSERT_THAT(recvfrom(packet_sock.get(), read_buf, sizeof(read_buf), 0, + reinterpret_cast<struct sockaddr*>(&src), &src_len), + SyscallSucceedsWithValue(sizeof(ping_req))); + EXPECT_EQ(memcmp(read_buf, &ping_req, sizeof(ping_req)), 0); + ASSERT_EQ(src_len, sizeof(src)); + EXPECT_EQ(src.sll_family, AF_PACKET); + EXPECT_EQ(ntohs(src.sll_protocol), ETH_P_IP); + EXPECT_EQ(src.sll_ifindex, link.index); + EXPECT_EQ(src.sll_pkttype, PACKET_HOST); + EXPECT_EQ(src.sll_halen, hardware_address_length); + if (IsRunningOnGvisor()) { + // TODO(https://gvisor.dev/issue/6531): Check this field for the right + // hardware type. + EXPECT_EQ(src.sll_hatype, 0); + } else { + EXPECT_EQ(src.sll_hatype, ARPHRD_NONE); + } + } + + { + const struct sockaddr_ll dest = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IP), + .sll_ifindex = link.index, + .sll_halen = hardware_address_length, + }; + + const ping_ip_pkt ping_req = CreatePingIPPacket(kTapIPAddr, kTapPeerIPAddr); + ASSERT_THAT( + sendto(packet_sock.get(), &ping_req, sizeof(ping_req), 0, + reinterpret_cast<const struct sockaddr*>(&dest), sizeof(dest)), + SyscallSucceedsWithValue(sizeof(ping_req))); + + // Loop until we receive the packet we expect - the kernel may send packets + // we do not care about. + while (true) { + // Wait for the TUN interface to become readable. + pollfd pfd = { + .fd = tun.get(), + .events = POLLIN, + }; + ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kInfiniteTimeout), + SyscallSucceedsWithValue(1)); + + char read_buf[sizeof(ping_req) + 1]; + int n = read(tun.get(), &read_buf, sizeof(read_buf)); + ASSERT_THAT(n, SyscallSucceeds()); + if (n == sizeof(ping_req) && + memcmp(read_buf, &ping_req, sizeof(ping_req)) == 0) { + break; + } + } + } +} + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc index 3353e58cb..0c0a16074 100644 --- a/test/syscalls/linux/udp_socket.cc +++ b/test/syscalls/linux/udp_socket.cc @@ -18,6 +18,8 @@ #include <netinet/ip_icmp.h> #include <ctime> +#include <utility> +#include <vector> #ifdef __linux__ #include <linux/errqueue.h> @@ -600,6 +602,67 @@ TEST_P(UdpSocketTest, DisconnectAfterBind) { SyscallFailsWithErrno(ENOTCONN)); } +void ConnectThenDisconnect(const FileDescriptor& sock, + const sockaddr* bind_addr, + const socklen_t expected_addrlen) { + // Connect the bound socket. + ASSERT_THAT(connect(sock.get(), bind_addr, expected_addrlen), + SyscallSucceeds()); + + // Disconnect. + { + sockaddr_storage unspec = {.ss_family = AF_UNSPEC}; + ASSERT_THAT(connect(sock.get(), AsSockAddr(&unspec), sizeof(unspec)), + SyscallSucceeds()); + } + { + // Check that we're not in a bound state. + sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + ASSERT_THAT(getsockname(sock.get(), AsSockAddr(&addr), &addrlen), + SyscallSucceeds()); + ASSERT_EQ(addrlen, expected_addrlen); + // Everything should be the zero value except the address family. + sockaddr_storage expected = { + .ss_family = bind_addr->sa_family, + }; + EXPECT_EQ(memcmp(&expected, &addr, expected_addrlen), 0); + } + + { + // We are not connected so we have no peer. + sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + EXPECT_THAT(getpeername(sock.get(), AsSockAddr(&addr), &addrlen), + SyscallFailsWithErrno(ENOTCONN)); + } +} + +TEST_P(UdpSocketTest, DisconnectAfterBindToUnspecAndConnect) { + ASSERT_NO_ERRNO(BindLoopback()); + + sockaddr_storage unspec = {.ss_family = AF_UNSPEC}; + int bind_res = bind(sock_.get(), AsSockAddr(&unspec), sizeof(unspec)); + if ((!IsRunningOnGvisor() || IsRunningWithHostinet()) && + GetFamily() == AF_INET) { + // Linux allows this for undocumented compatibility reasons: + // https://github.com/torvalds/linux/commit/29c486df6a208432b370bd4be99ae1369ede28d8. + // + // TODO(https://gvisor.dev/issue/6575): Match Linux's behaviour. + ASSERT_THAT(bind_res, SyscallSucceeds()); + } else { + ASSERT_THAT(bind_res, SyscallFailsWithErrno(EAFNOSUPPORT)); + } + + ASSERT_NO_FATAL_FAILURE(ConnectThenDisconnect(sock_, bind_addr_, addrlen_)); +} + +TEST_P(UdpSocketTest, DisconnectAfterConnectWithoutBind) { + ASSERT_NO_ERRNO(BindLoopback()); + + ASSERT_NO_FATAL_FAILURE(ConnectThenDisconnect(sock_, bind_addr_, addrlen_)); +} + TEST_P(UdpSocketTest, BindToAnyConnnectToLocalhost) { ASSERT_NO_ERRNO(BindAny()); @@ -1685,6 +1748,148 @@ TEST_P(UdpSocketTest, TimestampIoctlPersistence) { ASSERT_EQ(tv.tv_usec, tv2.tv_usec); } +// TOS and TCLASS values may be different but IPv6 sockets with IPv4-mapped-IPv6 +// addresses use TOS (IPv4), not TCLASS (IPv6). +TEST_P(UdpSocketTest, DifferentTOSAndTClass) { + const int kFamily = GetFamily(); + constexpr int kToS = IPTOS_LOWDELAY; + constexpr int kTClass = IPTOS_THROUGHPUT; + ASSERT_NE(kToS, kTClass); + + if (kFamily == AF_INET6) { + ASSERT_THAT(setsockopt(sock_.get(), SOL_IPV6, IPV6_TCLASS, &kTClass, + sizeof(kTClass)), + SyscallSucceeds()); + + // Marking an IPv6 socket as IPv6 only should not affect the ability to + // configure IPv4 socket options as the V6ONLY flag may later be disabled so + // that applications may use the socket to send/receive IPv4 packets. + constexpr int on = 1; + ASSERT_THAT(setsockopt(sock_.get(), SOL_IPV6, IPV6_V6ONLY, &on, sizeof(on)), + SyscallSucceeds()); + } + + ASSERT_THAT(setsockopt(sock_.get(), SOL_IP, IP_TOS, &kToS, sizeof(kToS)), + SyscallSucceeds()); + + if (kFamily == AF_INET6) { + int got_tclass; + socklen_t got_tclass_len = sizeof(got_tclass); + ASSERT_THAT(getsockopt(sock_.get(), SOL_IPV6, IPV6_TCLASS, &got_tclass, + &got_tclass_len), + SyscallSucceeds()); + ASSERT_EQ(got_tclass_len, sizeof(got_tclass)); + EXPECT_EQ(got_tclass, kTClass); + } + + { + int got_tos; + socklen_t got_tos_len = sizeof(got_tos); + ASSERT_THAT(getsockopt(sock_.get(), SOL_IP, IP_TOS, &got_tos, &got_tos_len), + SyscallSucceeds()); + ASSERT_EQ(got_tos_len, sizeof(got_tos)); + EXPECT_EQ(got_tos, kToS); + } + + auto test_send = [this](sockaddr_storage addr, + std::function<void(const cmsghdr*)> cb) { + FileDescriptor bind = ASSERT_NO_ERRNO_AND_VALUE( + Socket(addr.ss_family, SOCK_DGRAM, IPPROTO_UDP)); + ASSERT_NO_ERRNO(BindSocket(bind.get(), reinterpret_cast<sockaddr*>(&addr))); + ASSERT_THAT(setsockopt(bind.get(), SOL_IP, IP_RECVTOS, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceeds()); + if (addr.ss_family == AF_INET6) { + ASSERT_THAT(setsockopt(bind.get(), SOL_IPV6, IPV6_RECVTCLASS, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceeds()); + } + + char sent_data[1024]; + iovec sent_iov = { + .iov_base = sent_data, + .iov_len = sizeof(sent_data), + }; + msghdr sent_msg = { + .msg_name = &addr, + .msg_namelen = sizeof(addr), + .msg_iov = &sent_iov, + .msg_iovlen = 1, + }; + ASSERT_THAT(RetryEINTR(sendmsg)(sock_.get(), &sent_msg, 0), + SyscallSucceedsWithValue(sizeof(sent_data))); + + char received_data[sizeof(sent_data) + 1]; + iovec received_iov = { + .iov_base = received_data, + .iov_len = sizeof(received_data), + }; + std::vector<char> received_cmsgbuf(CMSG_SPACE(sizeof(int8_t))); + msghdr received_msg = { + .msg_iov = &received_iov, + .msg_iovlen = 1, + .msg_control = received_cmsgbuf.data(), + .msg_controllen = static_cast<socklen_t>(received_cmsgbuf.size()), + }; + ASSERT_THAT(RetryEINTR(recvmsg)(bind.get(), &received_msg, 0), + SyscallSucceedsWithValue(sizeof(sent_data))); + + cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg); + ASSERT_NE(cmsg, nullptr); + ASSERT_NO_FATAL_FAILURE(cb(cmsg)); + EXPECT_EQ(CMSG_NXTHDR(&received_msg, cmsg), nullptr); + }; + + if (kFamily == AF_INET6) { + SCOPED_TRACE( + "Send IPv4 loopback packet using IPv6 socket via IPv4-mapped-IPv6"); + + constexpr int off = 0; + ASSERT_THAT( + setsockopt(sock_.get(), SOL_IPV6, IPV6_V6ONLY, &off, sizeof(off)), + SyscallSucceeds()); + + // Send a packet and make sure that the ToS value in the IPv4 header is + // the configured IPv4 ToS Value and not the IPv6 Traffic Class value even + // though we use an IPv6 socket to send an IPv4 packet. + ASSERT_NO_FATAL_FAILURE( + test_send(V4MappedLoopback().addr, [kToS](const cmsghdr* cmsg) { + EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int8_t))); + EXPECT_EQ(cmsg->cmsg_level, SOL_IP); + EXPECT_EQ(cmsg->cmsg_type, IP_TOS); + int8_t received; + memcpy(&received, CMSG_DATA(cmsg), sizeof(received)); + EXPECT_EQ(received, kToS); + })); + } + + { + SCOPED_TRACE("Send loopback packet"); + + ASSERT_NO_FATAL_FAILURE(test_send( + InetLoopbackAddr(), [kFamily, kTClass, kToS](const cmsghdr* cmsg) { + switch (kFamily) { + case AF_INET: { + EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int8_t))); + EXPECT_EQ(cmsg->cmsg_level, SOL_IP); + EXPECT_EQ(cmsg->cmsg_type, IP_TOS); + int8_t received; + memcpy(&received, CMSG_DATA(cmsg), sizeof(received)); + EXPECT_EQ(received, kToS); + } break; + case AF_INET6: { + EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int32_t))); + EXPECT_EQ(cmsg->cmsg_level, SOL_IPV6); + EXPECT_EQ(cmsg->cmsg_type, IPV6_TCLASS); + int32_t received; + memcpy(&received, CMSG_DATA(cmsg), sizeof(received)); + EXPECT_EQ(received, kTClass); + } break; + } + })); + } +} + // Test that a socket with IP_TOS or IPV6_TCLASS set will set the TOS byte on // outgoing packets, and that a receiving socket with IP_RECVTOS or // IPV6_RECVTCLASS will create the corresponding control message. diff --git a/test/util/BUILD b/test/util/BUILD index 4a4401ba8..5b6bcb32c 100644 --- a/test/util/BUILD +++ b/test/util/BUILD @@ -1,4 +1,4 @@ -load("//tools:defs.bzl", "cc_library", "cc_test", "coreutil", "default_net_util", "gbenchmark", "gtest", "select_system") +load("//tools:defs.bzl", "cc_library", "cc_test", "coreutil", "default_net_util", "gbenchmark_internal", "gtest", "select_system") package( default_visibility = ["//:sandbox"], @@ -8,13 +8,20 @@ package( cc_library( name = "capability_util", testonly = 1, - srcs = ["capability_util.cc"], - hdrs = ["capability_util.h"], + srcs = [ + "fuchsia_capability_util.cc", + "linux_capability_util.cc", + ], + hdrs = [ + "capability_util.h", + "linux_capability_util.h", + ], deps = [ ":cleanup", ":memory_util", ":posix_error", ":save_util", + ":socket_util", ":test_util", "@com_google_absl//absl/strings", ], @@ -288,7 +295,7 @@ cc_library( "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/time", gtest, - gbenchmark, + gbenchmark_internal, ], ) @@ -343,6 +350,18 @@ cc_library( ) cc_library( + name = "benchmark_main", + testonly = 1, + linkstatic = 1, + deps = [ + ":test_util", + "@com_google_absl//absl/flags:flag", + gtest, + gbenchmark_internal, + ], +) + +cc_library( name = "epoll_util", testonly = 1, srcs = ["epoll_util.cc"], diff --git a/test/util/capability_util.h b/test/util/capability_util.h index c4b0feade..ac1a1b32b 100644 --- a/test/util/capability_util.h +++ b/test/util/capability_util.h @@ -17,112 +17,32 @@ #ifndef GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ #define GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ -#ifdef __linux__ - -#include <errno.h> -#include <linux/capability.h> -#include <sys/syscall.h> -#include <unistd.h> - -#include "test/util/cleanup.h" #include "test/util/posix_error.h" -#include "test/util/save_util.h" -#include "test/util/test_util.h" -#ifndef _LINUX_CAPABILITY_VERSION_3 -#error Expecting _LINUX_CAPABILITY_VERSION_3 support +#if defined(__Fuchsia__) +// Nothing to include. +#elif defined(__linux__) +#include "test/util/linux_capability_util.h" +#else +#error "Unhandled platform" #endif namespace gvisor { namespace testing { -// HaveCapability returns true if the process has the specified EFFECTIVE -// capability. -inline PosixErrorOr<bool> HaveCapability(int cap) { - if (!cap_valid(cap)) { - return PosixError(EINVAL, "Invalid capability"); - } - - struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; - struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); - MaybeSave(); - - return (caps[CAP_TO_INDEX(cap)].effective & CAP_TO_MASK(cap)) != 0; -} - -// SetCapability sets the specified EFFECTIVE capability. -inline PosixError SetCapability(int cap, bool set) { - if (!cap_valid(cap)) { - return PosixError(EINVAL, "Invalid capability"); - } - - struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; - struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); - MaybeSave(); - - if (set) { - caps[CAP_TO_INDEX(cap)].effective |= CAP_TO_MASK(cap); - } else { - caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap); - } - header = {_LINUX_CAPABILITY_VERSION_3, 0}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps)); - MaybeSave(); - - return NoError(); -} - -// DropPermittedCapability drops the specified PERMITTED. The EFFECTIVE -// capabilities must be a subset of PERMITTED, so those are dropped as well. -inline PosixError DropPermittedCapability(int cap) { - if (!cap_valid(cap)) { - return PosixError(EINVAL, "Invalid capability"); - } - - struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; - struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); - MaybeSave(); - - caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap); - caps[CAP_TO_INDEX(cap)].permitted &= ~CAP_TO_MASK(cap); - - header = {_LINUX_CAPABILITY_VERSION_3, 0}; - RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps)); - MaybeSave(); - - return NoError(); -} - -PosixErrorOr<bool> CanCreateUserNamespace(); - -class AutoCapability { - public: - AutoCapability(int cap, bool set) : cap_(cap), set_(set) { - const bool has = EXPECT_NO_ERRNO_AND_VALUE(HaveCapability(cap)); - if (set != has) { - EXPECT_NO_ERRNO(SetCapability(cap_, set_)); - applied_ = true; - } - } - - ~AutoCapability() { - if (applied_) { - EXPECT_NO_ERRNO(SetCapability(cap_, !set_)); - } - } +// HaveRawIPSocketCapability returns whether or not the process has access to +// raw IP sockets. +// +// Returns an error when raw IP socket access cannot be determined. +PosixErrorOr<bool> HaveRawIPSocketCapability(); - private: - int cap_; - bool set_; - bool applied_ = false; -}; +// HavePacketSocketCapability returns whether or not the process has access to +// packet sockets. +// +// Returns an error when packet socket access cannot be determined. +PosixErrorOr<bool> HavePacketSocketCapability(); } // namespace testing } // namespace gvisor -#endif // __linux__ - #endif // GVISOR_TEST_UTIL_CAPABILITY_UTIL_H_ diff --git a/test/util/cgroup_util.cc b/test/util/cgroup_util.cc index 977993f41..df3c57b87 100644 --- a/test/util/cgroup_util.cc +++ b/test/util/cgroup_util.cc @@ -25,12 +25,26 @@ namespace gvisor { namespace testing { -Cgroup::Cgroup(std::string_view path) : cgroup_path_(path) { +Cgroup::Cgroup(absl::string_view path) : cgroup_path_(path) { id_ = ++Cgroup::next_id_; std::cerr << absl::StreamFormat("[cg#%d] <= %s", id_, cgroup_path_) << std::endl; } +PosixErrorOr<Cgroup> Cgroup::RecursivelyCreate(absl::string_view path) { + RETURN_IF_ERRNO(RecursivelyCreateDir(path)); + return Cgroup(path); +} + +PosixErrorOr<Cgroup> Cgroup::Create(absl::string_view path) { + RETURN_IF_ERRNO(Mkdir(path)); + return Cgroup(path); +} + +PosixErrorOr<Cgroup> Cgroup::CreateChild(absl::string_view name) const { + return Cgroup::Create(JoinPath(Path(), name)); +} + PosixErrorOr<std::string> Cgroup::ReadControlFile( absl::string_view name) const { std::string buf; @@ -93,7 +107,7 @@ PosixErrorOr<absl::flat_hash_set<pid_t>> Cgroup::ParsePIDList( absl::string_view data) const { absl::flat_hash_set<pid_t> res; std::vector<absl::string_view> lines = absl::StrSplit(data, '\n'); - for (const std::string_view& line : lines) { + for (const absl::string_view& line : lines) { if (line.empty()) { continue; } diff --git a/test/util/cgroup_util.h b/test/util/cgroup_util.h index e3f696a89..ccc7219e3 100644 --- a/test/util/cgroup_util.h +++ b/test/util/cgroup_util.h @@ -34,8 +34,20 @@ class Cgroup { uint64_t id() const { return id_; } + // RecursivelyCreate creates cgroup specified by path, including all + // components leading up to path. Path should end inside a cgroupfs mount. If + // path already exists, RecursivelyCreate does nothing and silently succeeds. + static PosixErrorOr<Cgroup> RecursivelyCreate(std::string_view path); + + // Creates a new cgroup at path. The parent directory must exist and be a + // cgroupfs directory. + static PosixErrorOr<Cgroup> Create(std::string_view path); + const std::string& Path() const { return cgroup_path_; } + // Creates a child cgroup under this cgroup with the given name. + PosixErrorOr<Cgroup> CreateChild(std::string_view name) const; + std::string Relpath(absl::string_view leaf) const { return JoinPath(cgroup_path_, leaf); } diff --git a/test/util/fs_util.cc b/test/util/fs_util.cc index 483ae848d..253411858 100644 --- a/test/util/fs_util.cc +++ b/test/util/fs_util.cc @@ -201,7 +201,8 @@ PosixError UnlinkAt(const FileDescriptor& dfd, absl::string_view path, PosixError Mkdir(absl::string_view path, int mode) { int res = mkdir(std::string(path).c_str(), mode); if (res < 0) { - return PosixError(errno, absl::StrCat("mkdir ", path, " mode ", mode)); + return PosixError(errno, + absl::StrFormat("mkdir \"%s\" mode %#o", path, mode)); } return NoError(); diff --git a/test/util/fuchsia_capability_util.cc b/test/util/fuchsia_capability_util.cc new file mode 100644 index 000000000..bbe8643e7 --- /dev/null +++ b/test/util/fuchsia_capability_util.cc @@ -0,0 +1,73 @@ +// 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. + +#ifdef __Fuchsia__ + +#include <netinet/if_ether.h> +#include <netinet/in.h> +#include <sys/socket.h> + +#include "test/util/socket_util.h" + +namespace gvisor { +namespace testing { + +// On Linux, access to raw IP and packet socket is controlled by a single +// capability (CAP_NET_RAW). However on Fuchsia, access to raw IP and packet +// sockets are controlled by separate capabilities/protocols. + +namespace { + +PosixErrorOr<bool> HaveSocketCapability(int domain, int type, int protocol) { + // Fuchsia does not have a platform supported way to check the protocols made + // available to a sandbox. As a workaround, simply try to create the specified + // socket and assume no access if we get a no permissions error. + auto s = Socket(domain, type, protocol); + if (s.ok()) { + return true; + } + if (s.error().errno_value() == EPERM) { + return false; + } + return s.error(); +} + +} // namespace + +PosixErrorOr<bool> HaveRawIPSocketCapability() { + static PosixErrorOr<bool> result(false); + static std::once_flag once; + + std::call_once(once, [&]() { + result = HaveSocketCapability(AF_INET, SOCK_RAW, IPPROTO_UDP); + }); + + return result; +} + +PosixErrorOr<bool> HavePacketSocketCapability() { + static PosixErrorOr<bool> result(false); + static std::once_flag once; + + std::call_once(once, [&]() { + result = HaveSocketCapability(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + }); + + return result; +} + +} // namespace testing +} // namespace gvisor + +#endif // __Fuchsia__ diff --git a/test/util/capability_util.cc b/test/util/linux_capability_util.cc index 3bf218128..7218aa4ac 100644 --- a/test/util/capability_util.cc +++ b/test/util/linux_capability_util.cc @@ -14,7 +14,7 @@ #ifdef __linux__ -#include "test/util/capability_util.h" +#include "test/util/linux_capability_util.h" #include <linux/capability.h> #include <sched.h> @@ -32,6 +32,14 @@ namespace gvisor { namespace testing { +PosixErrorOr<bool> HaveRawIPSocketCapability() { + return HaveCapability(CAP_NET_RAW); +} + +PosixErrorOr<bool> HavePacketSocketCapability() { + return HaveCapability(CAP_NET_RAW); +} + PosixErrorOr<bool> CanCreateUserNamespace() { // The most reliable way to determine if userns creation is possible is by // trying to create one; see below. diff --git a/test/util/linux_capability_util.h b/test/util/linux_capability_util.h new file mode 100644 index 000000000..be94ebd19 --- /dev/null +++ b/test/util/linux_capability_util.h @@ -0,0 +1,128 @@ +// Copyright 2018 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. + +// Utilities for testing capabilities on Linux. + +#ifndef GVISOR_TEST_UTIL_LINUX_CAPABILITY_UTIL_H_ +#define GVISOR_TEST_UTIL_LINUX_CAPABILITY_UTIL_H_ + +#ifdef __linux__ + +#include <errno.h> +#include <linux/capability.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include "test/util/cleanup.h" +#include "test/util/posix_error.h" +#include "test/util/save_util.h" +#include "test/util/test_util.h" + +#ifndef _LINUX_CAPABILITY_VERSION_3 +#error Expecting _LINUX_CAPABILITY_VERSION_3 support +#endif + +namespace gvisor { +namespace testing { + +// HaveCapability returns true if the process has the specified EFFECTIVE +// capability. +inline PosixErrorOr<bool> HaveCapability(int cap) { + if (!cap_valid(cap)) { + return PosixError(EINVAL, "Invalid capability"); + } + + struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; + struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); + MaybeSave(); + + return (caps[CAP_TO_INDEX(cap)].effective & CAP_TO_MASK(cap)) != 0; +} + +// SetCapability sets the specified EFFECTIVE capability. +inline PosixError SetCapability(int cap, bool set) { + if (!cap_valid(cap)) { + return PosixError(EINVAL, "Invalid capability"); + } + + struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; + struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); + MaybeSave(); + + if (set) { + caps[CAP_TO_INDEX(cap)].effective |= CAP_TO_MASK(cap); + } else { + caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap); + } + header = {_LINUX_CAPABILITY_VERSION_3, 0}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps)); + MaybeSave(); + + return NoError(); +} + +// DropPermittedCapability drops the specified PERMITTED. The EFFECTIVE +// capabilities must be a subset of PERMITTED, so those are dropped as well. +inline PosixError DropPermittedCapability(int cap) { + if (!cap_valid(cap)) { + return PosixError(EINVAL, "Invalid capability"); + } + + struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0}; + struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capget, &header, &caps)); + MaybeSave(); + + caps[CAP_TO_INDEX(cap)].effective &= ~CAP_TO_MASK(cap); + caps[CAP_TO_INDEX(cap)].permitted &= ~CAP_TO_MASK(cap); + + header = {_LINUX_CAPABILITY_VERSION_3, 0}; + RETURN_ERROR_IF_SYSCALL_FAIL(syscall(__NR_capset, &header, &caps)); + MaybeSave(); + + return NoError(); +} + +PosixErrorOr<bool> CanCreateUserNamespace(); + +class AutoCapability { + public: + AutoCapability(int cap, bool set) : cap_(cap), set_(set) { + const bool has = EXPECT_NO_ERRNO_AND_VALUE(HaveCapability(cap)); + if (set != has) { + EXPECT_NO_ERRNO(SetCapability(cap_, set_)); + applied_ = true; + } + } + + ~AutoCapability() { + if (applied_) { + EXPECT_NO_ERRNO(SetCapability(cap_, !set_)); + } + } + + private: + int cap_; + bool set_; + bool applied_ = false; +}; + +} // namespace testing +} // namespace gvisor + +#endif // __linux__ + +#endif // GVISOR_TEST_UTIL_LINUX_CAPABILITY_UTIL_H_ diff --git a/test/util/socket_util.cc b/test/util/socket_util.cc index f2360b732..650b422ae 100644 --- a/test/util/socket_util.cc +++ b/test/util/socket_util.cc @@ -57,7 +57,19 @@ Creator<FileDescriptor> SyscallSocketCreator(int domain, int type, PosixErrorOr<struct sockaddr_un> UniqueUnixAddr(bool abstract, int domain) { struct sockaddr_un addr = {}; + +#ifdef ANDROID + // Using NewTempAbsPath() can cause the tmp directory path to exceed the max + // length (i.e., sizeof(addr.sun_path)). + // + // However, existing systems that are built with the ANDROID configuration + // have their temp directory in a different location, and must respect the + // TEST_TMPDIR. + std::string path = NewTempAbsPath(); +#else std::string path = NewTempAbsPathInDir("/tmp"); +#endif // ANDROID + if (path.size() >= sizeof(addr.sun_path)) { return PosixError(EINVAL, "Unable to generate a temp path of appropriate length"); 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); |