From a7b512f44ed5748828ca5ed55530031d834b4760 Mon Sep 17 00:00:00 2001 From: Nayana Bidari Date: Thu, 10 Oct 2024 11:02:17 -0700 Subject: [PATCH] Add a C/R test for TCP listening connection. This CL adds a test which verifies that the listening connections are restored correctly after checkpoint. The test checkpoints when the tcp server is in the listen state and verifies the read/write operations after the tcp server is restored. PiperOrigin-RevId: 684505213 --- images/basic/integrationtest/Dockerfile | 1 + images/basic/integrationtest/tcp_server.c | 87 +++++++++++++++++++++++ test/e2e/integration_test.go | 83 +++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 images/basic/integrationtest/tcp_server.c diff --git a/images/basic/integrationtest/Dockerfile b/images/basic/integrationtest/Dockerfile index 5d4789a15..8da8fc082 100644 --- a/images/basic/integrationtest/Dockerfile +++ b/images/basic/integrationtest/Dockerfile @@ -14,6 +14,7 @@ RUN gcc -O2 -o link_test link_test.c RUN gcc -O2 -o test_sticky test_sticky.c RUN gcc -O2 -o host_fd host_fd.c RUN gcc -O2 -o host_connect host_connect.c +RUN gcc -O2 -o tcp_server tcp_server.c # Add nonprivileged regular user named "nonroot". RUN groupadd --gid 1337 nonroot && \ diff --git a/images/basic/integrationtest/tcp_server.c b/images/basic/integrationtest/tcp_server.c new file mode 100644 index 000000000..fd8e3e72a --- /dev/null +++ b/images/basic/integrationtest/tcp_server.c @@ -0,0 +1,87 @@ +// Copyright 2024 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 +#include +#include +#include +#include +#include +#include + +#define port 9000 + +int main(int argc, char** argv) { + int fd; + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + perror("socket failed"); + exit(EXIT_FAILURE); + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + perror("bind failed"); + exit(EXIT_FAILURE); + } + if (listen(fd, 1) < 0) { + perror("listen failed"); + exit(EXIT_FAILURE); + } + + int addrlen = sizeof(addr); + int conn; + conn = accept(fd, (struct sockaddr*)&addr, (socklen_t*)&addrlen); + if (conn < 0) { + perror("accept failed"); + exit(EXIT_FAILURE); + } + close(conn); + + while (1) { + conn = accept(fd, (struct sockaddr*)&addr, (socklen_t*)&addrlen); + if (conn >= 0) { + break; + } + if (errno != EINTR) { + perror("accept failed"); + exit(EXIT_FAILURE); + } + } + + if (conn < 0) { + perror("accept failed"); + exit(EXIT_FAILURE); + } + + int n = 0; + char send_buf[24] = "Hello!"; + n = write(conn, send_buf, strlen(send_buf)); + if (n < 0) { + perror("ERROR writing to socket"); + exit(1); + } + + char buffer[256]; + bzero(buffer, sizeof(buffer)); + n = read(conn, buffer, sizeof(buffer)); + if (n < 0) { + perror("ERROR reading from socket"); + return n; + } + + return 0; +} diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go index bcc24b80c..fa1da83fd 100644 --- a/test/e2e/integration_test.go +++ b/test/e2e/integration_test.go @@ -1212,3 +1212,86 @@ func TestCheckpointResume(t *testing.T) { t.Fatalf("docker kill failed: %v", err) } } + +// Test to check restore of a TCP listening connection. +func TestCheckpointRestoreListeningConnection(t *testing.T) { + if !testutil.IsCheckpointSupported() { + t.Skip("Checkpoint is not supported.") + } + dockerutil.EnsureDockerExperimentalEnabled() + + ctx := context.Background() + d := dockerutil.MakeContainer(ctx, t) + defer d.CleanUp(ctx) + + const port = 9000 + opts := dockerutil.RunOpts{ + Image: "basic/integrationtest", + Ports: []int{port}, + } + + // Start the tcp server. + if err := d.Spawn(ctx, opts, "./tcp_server"); err != nil { + t.Fatalf("docker run failed: %v", err) + } + + var ( + ip net.IP + err error + conn net.Conn + ) + ip, err = d.FindIP(ctx, false) + if err != nil { + t.Fatalf("docker.FindIP failed: %v", err) + } + serverIP := ip.String() + ":" + strconv.Itoa(port) + const timeout = 1 * time.Minute + for { + conn, err = net.DialTimeout("tcp", serverIP, timeout) + if err == nil { + break + } + } + conn.Close() + + // Create a snapshot. + const checkpointFile = "networktest" + if err := d.Checkpoint(ctx, checkpointFile); err != nil { + t.Fatalf("docker checkpoint failed: %v", err) + } + if err := d.WaitTimeout(ctx, defaultWait); err != nil { + t.Fatalf("wait failed: %v", err) + } + // TODO(b/143498576): Remove Poll after github.com/moby/moby/issues/38963 is fixed. + if err := testutil.Poll(func() error { return d.Restore(ctx, checkpointFile) }, defaultWait); err != nil { + t.Fatalf("docker restore failed: %v", err) + } + + var ( + newIP net.IP + newConn net.Conn + ) + newIP, err = d.FindIP(ctx, false) + if err != nil { + t.Fatalf("docker.FindIP failed: %v", err) + } + newserverIP := newIP.String() + ":" + strconv.Itoa(port) + newConn, err = net.DialTimeout("tcp", newserverIP, timeout) + if err != nil { + t.Fatalf("Error connecting to server: %v", err) + } + defer newConn.Close() + + readBuf := make([]byte, 32) + if _, err := newConn.Read(readBuf); err != nil { + t.Fatalf("Read failed: %v", err) + } + + if _, err := newConn.Write([]byte("Hello!")); err != nil { + t.Fatalf("Write failed: %v", err) + } + + if err := d.Wait(ctx); err != nil { + t.Fatalf("Wait failed: %v", err) + } +}