Skip to content

Commit 6f3d0ec

Browse files
nybidarigvisor-bot
authored andcommitted
Add netstack s/r test for loopback connection.
The test verifies that established loopback connections are restored correctly after save. PiperOrigin-RevId: 813817428
1 parent f943594 commit 6f3d0ec

File tree

3 files changed

+184
-10
lines changed

3 files changed

+184
-10
lines changed

images/basic/integrationtest/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ RUN gcc -O2 -o host_fd host_fd.c
1616
RUN gcc -O2 -o host_connect host_connect.c
1717
RUN gcc -O2 -o tcp_server tcp_server.c
1818
RUN gcc -O2 -o tcp_stress_server tcp_stress_server.c -pthread
19+
RUN gcc -O2 -o tcp_loopback tcp_loopback.c
1920

2021
# Add nonprivileged regular user named "nonroot".
2122
RUN groupadd --gid 1337 nonroot && \
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#include <arpa/inet.h>
2+
#include <errno.h>
3+
#include <netinet/in.h>
4+
#include <stdio.h>
5+
#include <stdlib.h>
6+
#include <string.h>
7+
#include <sys/socket.h>
8+
#include <sys/types.h>
9+
#include <sys/wait.h>
10+
#include <unistd.h>
11+
12+
#define EXTERNAL_PORT 9000
13+
#define LOCAL_PORT 9001
14+
15+
// Accept the connection and read.
16+
int accept_and_read(int server_fd, struct sockaddr_in addr, int should_read) {
17+
int new_fd;
18+
socklen_t addrlen = sizeof(addr);
19+
20+
while (1) {
21+
new_fd = accept(server_fd, (struct sockaddr*)&addr, &addrlen);
22+
if (new_fd >= 0) {
23+
break;
24+
}
25+
if (errno != EINTR) {
26+
perror("accept failed");
27+
exit(EXIT_FAILURE);
28+
}
29+
}
30+
31+
if (new_fd < 0) {
32+
perror("accept failed");
33+
exit(EXIT_FAILURE);
34+
}
35+
36+
if (should_read > 0) {
37+
for (int i = 0; i < 10; i++) {
38+
char buffer[1024] = {0};
39+
ssize_t valread = read(new_fd, buffer, 1024);
40+
if (valread <= 0) {
41+
perror("Server: read failed");
42+
exit(EXIT_FAILURE);
43+
}
44+
printf("Server received: %s", buffer);
45+
}
46+
}
47+
return new_fd;
48+
}
49+
50+
int start_listen(struct sockaddr_in addr) {
51+
socklen_t addrlen = sizeof(addr);
52+
int server_fd;
53+
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
54+
perror("Server: socket failed");
55+
exit(EXIT_FAILURE);
56+
}
57+
if (bind(server_fd, (struct sockaddr*)&addr, addrlen) < 0) {
58+
perror("Server: bind failed");
59+
exit(EXIT_FAILURE);
60+
}
61+
if (listen(server_fd, 2) < 0) {
62+
perror("Server: listen");
63+
exit(EXIT_FAILURE);
64+
}
65+
return server_fd;
66+
}
67+
68+
int main() {
69+
// Start a listening socket on port 9001 which should be connected by a
70+
// loopback client.
71+
struct sockaddr_in address;
72+
address.sin_family = AF_INET;
73+
address.sin_port = htons(LOCAL_PORT);
74+
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
75+
int local_fd = start_listen(address);
76+
int local_new_fd;
77+
78+
pid_t pid = fork();
79+
if (pid < 0) {
80+
perror("fork failed");
81+
return 1;
82+
}
83+
if (pid == 0) {
84+
local_new_fd = accept_and_read(local_fd, address, 1 /* should_read */);
85+
return 1;
86+
}
87+
88+
// Start a listening socket on port 9000 which should be connected by an
89+
// external client.
90+
struct sockaddr_in ext_addr;
91+
ext_addr.sin_family = AF_INET;
92+
ext_addr.sin_port = htons(EXTERNAL_PORT);
93+
ext_addr.sin_addr.s_addr = htonl(INADDR_ANY);
94+
int ext_fd = start_listen(ext_addr);
95+
96+
// Connect to the local loopback server.
97+
int connect_fd = socket(AF_INET, SOCK_STREAM, 0);
98+
if (connect_fd < 0) {
99+
perror("Client: socket (Connect)");
100+
return 1;
101+
}
102+
int ret;
103+
do {
104+
ret = connect(connect_fd, (struct sockaddr*)&address, sizeof(address));
105+
} while (ret == -1 && errno == EINTR);
106+
if (ret < 0) {
107+
perror("Client: connect failed");
108+
exit(EXIT_FAILURE);
109+
}
110+
111+
// Write and read with the loopback server.
112+
for (int i = 0; i < 10; i++) {
113+
const char* message = "Hello from Client 9000!\n";
114+
int num_sent = send(connect_fd, message, strlen(message), 0);
115+
if ((num_sent == -1) || (num_sent < strlen(message))) {
116+
perror("send failed");
117+
exit(EXIT_FAILURE);
118+
}
119+
}
120+
121+
int ext_new_fd = accept_and_read(ext_fd, ext_addr, 0 /* should_read */);
122+
close(ext_new_fd);
123+
ext_new_fd = -1;
124+
125+
// After connecting with the external server, a checkpoint is issued to the
126+
// sandbox. Everything below this happens after restore.
127+
ext_new_fd = accept_and_read(ext_fd, ext_addr, 0 /* should_read */);
128+
close(ext_new_fd);
129+
130+
pid_t new_pid = fork();
131+
int restore_fd;
132+
if (new_pid < 0) {
133+
perror("fork failed");
134+
return 1;
135+
}
136+
if (new_pid == 0) {
137+
// Listening connections should be restored, start accepting new connections
138+
// after restore.
139+
restore_fd = accept_and_read(local_fd, address, 1 /* should_read */);
140+
return 1;
141+
}
142+
143+
// connect_fd should be restored, read and write to the local server.
144+
for (int i = 0; i < 10; i++) {
145+
const char* message = "Hello from Client 9000!\n";
146+
int num_sent = send(connect_fd, message, strlen(message), 0);
147+
if ((num_sent == -1) || (num_sent < strlen(message))) {
148+
perror("send failed");
149+
exit(EXIT_FAILURE);
150+
}
151+
}
152+
printf("\nProgram finished successfully.\n");
153+
return 0;
154+
}

test/e2e/integration_test.go

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,7 +1268,7 @@ func connectAndReadWrite(t *testing.T, serverIP string, port int) {
12681268
}
12691269
}
12701270

1271-
func testCheckpointRestoreListeningConnection(ctx context.Context, t *testing.T, d *dockerutil.Container, fName string, numConn int) {
1271+
func testCheckpointRestoreTCPConnection(ctx context.Context, t *testing.T, d *dockerutil.Container, fName string, numConn int, restoreLoopback bool) {
12721272
defer d.CleanUp(ctx)
12731273

12741274
opts := dockerutil.RunOpts{
@@ -1302,15 +1302,19 @@ func testCheckpointRestoreListeningConnection(ctx context.Context, t *testing.T,
13021302
if err != nil {
13031303
t.Fatalf("docker.FindIP failed: %v", err)
13041304
}
1305-
if numConn > 1 {
1305+
1306+
if restoreLoopback {
13061307
connectWithTCPServer(t, newIP.String(), port, numConn)
1307-
if err := d.Kill(ctx); err != nil {
1308-
t.Fatalf("Wait failed: %v", err)
1308+
} else {
1309+
if numConn > 1 {
1310+
connectWithTCPServer(t, newIP.String(), port, numConn)
1311+
if err := d.Kill(ctx); err != nil {
1312+
t.Fatalf("Kill failed: %v", err)
1313+
}
1314+
return
13091315
}
1310-
return
1316+
connectAndReadWrite(t, newIP.String(), port)
13111317
}
1312-
1313-
connectAndReadWrite(t, newIP.String(), port)
13141318
if err := d.Wait(ctx); err != nil {
13151319
t.Fatalf("Wait failed: %v", err)
13161320
}
@@ -1325,7 +1329,7 @@ func TestRestoreListenConn(t *testing.T) {
13251329

13261330
ctx := context.Background()
13271331
d := dockerutil.MakeContainer(ctx, t)
1328-
testCheckpointRestoreListeningConnection(ctx, t, d, "./tcp_server" /* fName */, 1 /* numConn */)
1332+
testCheckpointRestoreTCPConnection(ctx, t, d, "./tcp_server" /* fName */, 1 /* numConn */, false /* restoreLoopback */)
13291333
}
13301334

13311335
// Test to check restore of a TCP listening connection with netstack S/R.
@@ -1340,7 +1344,7 @@ func TestRestoreListenConnWithNetstackSR(t *testing.T) {
13401344

13411345
ctx := context.Background()
13421346
d := dockerutil.MakeContainerWithRuntime(ctx, t, "-save-restore-netstack")
1343-
testCheckpointRestoreListeningConnection(ctx, t, d, "./tcp_server" /* fName */, 1 /* numConn */)
1347+
testCheckpointRestoreTCPConnection(ctx, t, d, "./tcp_server" /* fName */, 1 /* numConn */, false /* restoreLoopback */)
13441348
}
13451349

13461350
// Test to check restore of multiple TCP listening connections with netstack S/R.
@@ -1355,7 +1359,22 @@ func TestRestoreMultipleListenConnWithNetstackSR(t *testing.T) {
13551359

13561360
ctx := context.Background()
13571361
d := dockerutil.MakeContainerWithRuntime(ctx, t, "-save-restore-netstack")
1358-
testCheckpointRestoreListeningConnection(ctx, t, d, "./tcp_stress_server" /* fName */, 100 /* numConn */)
1362+
testCheckpointRestoreTCPConnection(ctx, t, d, "./tcp_stress_server" /* fName */, 100 /* numConn */, false /* restoreLoopback */)
1363+
}
1364+
1365+
// Test to check restore of TCP established loopback connection with netstack S/R.
1366+
func TestRestoreLoopbackConnWithNetstackSR(t *testing.T) {
1367+
if !testutil.IsCheckpointSupported() {
1368+
t.Skip("Checkpoint is not supported.")
1369+
}
1370+
if !testutil.IsRunningWithSaveRestoreNetstack() {
1371+
t.Skip("Netstack save restore is not supported.")
1372+
}
1373+
dockerutil.EnsureDockerExperimentalEnabled()
1374+
1375+
ctx := context.Background()
1376+
d := dockerutil.MakeContainerWithRuntime(ctx, t, "-save-restore-netstack")
1377+
testCheckpointRestoreTCPConnection(ctx, t, d, "./tcp_loopback" /* fName */, 1 /* numConn */, true /* restoreLoopback */)
13591378
}
13601379

13611380
// Test to check if sudo works

0 commit comments

Comments
 (0)