Skip to content

Commit bc40393

Browse files
zelinggvisor-bot
authored andcommitted
Make tcp_noaccept_close_rst more robust
There used to be a race condition where we may call Close before the connection is established. Adding poll support so that we can eliminate this kind of race. Startblock: has LGTM from iyerm and then add reviewer tamird PiperOrigin-RevId: 354369130
1 parent d8c3302 commit bc40393

File tree

5 files changed

+130
-0
lines changed

5 files changed

+130
-0
lines changed

test/packetimpact/dut/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ cc_binary(
1414
grpcpp,
1515
"//test/packetimpact/proto:posix_server_cc_grpc_proto",
1616
"//test/packetimpact/proto:posix_server_cc_proto",
17+
"@com_google_absl//absl/strings:str_format",
1718
],
1819
)
1920

@@ -24,5 +25,6 @@ cc_binary(
2425
grpcpp,
2526
"//test/packetimpact/proto:posix_server_cc_grpc_proto",
2627
"//test/packetimpact/proto:posix_server_cc_proto",
28+
"@com_google_absl//absl/strings:str_format",
2729
],
2830
)

test/packetimpact/dut/posix_server.cc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <getopt.h>
1717
#include <netdb.h>
1818
#include <netinet/in.h>
19+
#include <poll.h>
1920
#include <stdio.h>
2021
#include <stdlib.h>
2122
#include <string.h>
@@ -30,6 +31,7 @@
3031
#include "include/grpcpp/security/server_credentials.h"
3132
#include "include/grpcpp/server_builder.h"
3233
#include "include/grpcpp/server_context.h"
34+
#include "absl/strings/str_format.h"
3335
#include "test/packetimpact/proto/posix_server.grpc.pb.h"
3436
#include "test/packetimpact/proto/posix_server.pb.h"
3537

@@ -256,6 +258,44 @@ class PosixImpl final : public posix_server::Posix::Service {
256258
return ::grpc::Status::OK;
257259
}
258260

261+
::grpc::Status Poll(::grpc::ServerContext *context,
262+
const ::posix_server::PollRequest *request,
263+
::posix_server::PollResponse *response) override {
264+
std::vector<struct pollfd> pfds;
265+
pfds.reserve(request->pfds_size());
266+
for (const auto &pfd : request->pfds()) {
267+
pfds.push_back({
268+
.fd = pfd.fd(),
269+
.events = static_cast<short>(pfd.events()),
270+
});
271+
}
272+
int ret = ::poll(pfds.data(), pfds.size(), request->timeout_millis());
273+
274+
response->set_ret(ret);
275+
if (ret < 0) {
276+
response->set_errno_(errno);
277+
} else {
278+
// Only pollfds that have non-empty revents are returned, the client can't
279+
// rely on indexes of the request array.
280+
for (const auto &pfd : pfds) {
281+
if (pfd.revents) {
282+
auto *proto_pfd = response->add_pfds();
283+
proto_pfd->set_fd(pfd.fd);
284+
proto_pfd->set_events(pfd.revents);
285+
}
286+
}
287+
if (int ready = response->pfds_size(); ret != ready) {
288+
return ::grpc::Status(
289+
::grpc::StatusCode::INTERNAL,
290+
absl::StrFormat(
291+
"poll's return value(%d) doesn't match the number of "
292+
"file descriptors that are actually ready(%d)",
293+
ret, ready));
294+
}
295+
}
296+
return ::grpc::Status::OK;
297+
}
298+
259299
::grpc::Status Send(::grpc::ServerContext *context,
260300
const ::posix_server::SendRequest *request,
261301
::posix_server::SendResponse *response) override {

test/packetimpact/proto/posix_server.proto

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,25 @@ message ListenResponse {
142142
int32 errno_ = 2; // "errno" may fail to compile in c++.
143143
}
144144

145+
// The events field is overloaded: when used for request, it is copied into the
146+
// events field of posix struct pollfd; when used for response, it is filled by
147+
// the revents field from the posix struct pollfd.
148+
message PollFd {
149+
int32 fd = 1;
150+
uint32 events = 2;
151+
}
152+
153+
message PollRequest {
154+
repeated PollFd pfds = 1;
155+
int32 timeout_millis = 2;
156+
}
157+
158+
message PollResponse {
159+
int32 ret = 1;
160+
int32 errno_ = 2; // "errno" may fail to compile in c++.
161+
repeated PollFd pfds = 3;
162+
}
163+
145164
message SendRequest {
146165
int32 sockfd = 1;
147166
bytes buf = 2;
@@ -226,6 +245,10 @@ service Posix {
226245
rpc GetSockOpt(GetSockOptRequest) returns (GetSockOptResponse);
227246
// Call listen() on the DUT.
228247
rpc Listen(ListenRequest) returns (ListenResponse);
248+
// Call poll() on the DUT. Only pollfds that have non-empty revents are
249+
// returned, the only way to tie the response back to the original request
250+
// is using the fd number.
251+
rpc Poll(PollRequest) returns (PollResponse);
229252
// Call send() on the DUT.
230253
rpc Send(SendRequest) returns (SendResponse);
231254
// Call sendto() on the DUT.

test/packetimpact/testbench/dut.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,56 @@ func (dut *DUT) ListenWithErrno(ctx context.Context, t *testing.T, sockfd, backl
486486
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
487487
}
488488

489+
// Poll calls poll on the DUT and causes a fatal test failure if it doesn't
490+
// succeed. If more control over error handling is needed, use PollWithErrno.
491+
// Only pollfds with non-empty revents are returned, the only way to tie the
492+
// response back to the original request is using the fd number.
493+
func (dut *DUT) Poll(t *testing.T, pfds []unix.PollFd, timeout time.Duration) []unix.PollFd {
494+
t.Helper()
495+
496+
ctx := context.Background()
497+
var cancel context.CancelFunc
498+
if timeout >= 0 {
499+
ctx, cancel = context.WithTimeout(ctx, timeout+RPCTimeout)
500+
defer cancel()
501+
}
502+
ret, result, err := dut.PollWithErrno(ctx, t, pfds, timeout)
503+
if ret < 0 {
504+
t.Fatalf("failed to poll: %s", err)
505+
}
506+
return result
507+
}
508+
509+
// PollWithErrno calls poll on the DUT.
510+
func (dut *DUT) PollWithErrno(ctx context.Context, t *testing.T, pfds []unix.PollFd, timeout time.Duration) (int32, []unix.PollFd, error) {
511+
t.Helper()
512+
513+
req := pb.PollRequest{
514+
TimeoutMillis: int32(timeout.Milliseconds()),
515+
}
516+
for _, pfd := range pfds {
517+
req.Pfds = append(req.Pfds, &pb.PollFd{
518+
Fd: pfd.Fd,
519+
Events: uint32(pfd.Events),
520+
})
521+
}
522+
resp, err := dut.posixServer.Poll(ctx, &req)
523+
if err != nil {
524+
t.Fatalf("failed to call Poll: %s", err)
525+
}
526+
if ret, npfds := resp.GetRet(), len(resp.GetPfds()); ret >= 0 && int(ret) != npfds {
527+
t.Fatalf("nonsensical poll response: ret(%d) != len(pfds)(%d)", ret, npfds)
528+
}
529+
var result []unix.PollFd
530+
for _, protoPfd := range resp.GetPfds() {
531+
result = append(result, unix.PollFd{
532+
Fd: protoPfd.GetFd(),
533+
Revents: int16(protoPfd.GetEvents()),
534+
})
535+
}
536+
return resp.GetRet(), result, syscall.Errno(resp.GetErrno_())
537+
}
538+
489539
// Send calls send on the DUT and causes a fatal test failure if it doesn't
490540
// succeed. If more control over the timeout or error handling is needed, use
491541
// SendWithErrno.

test/packetimpact/tests/tcp_noaccept_close_rst_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@ func TestTcpNoAcceptCloseReset(t *testing.T) {
3434
conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
3535
conn.Connect(t)
3636
defer conn.Close(t)
37+
// We need to wait for POLLIN event on listenFd to know the connection is
38+
// established. Otherwise there could be a race when we issue the Close
39+
// command prior to the DUT receiving the last ack of the handshake and
40+
// it will only respond RST instead of RST+ACK.
41+
timeout := time.Second
42+
pfds := dut.Poll(t, []unix.PollFd{{Fd: listenFd, Events: unix.POLLIN}}, timeout)
43+
if n := len(pfds); n != 1 {
44+
t.Fatalf("poll returned %d ready file descriptors, expected 1", n)
45+
}
46+
if readyFd := pfds[0].Fd; readyFd != listenFd {
47+
t.Fatalf("poll returned an fd %d that was not requested (%d)", readyFd, listenFd)
48+
}
49+
if got, want := pfds[0].Revents, int16(unix.POLLIN); got&want == 0 {
50+
t.Fatalf("poll returned no events in our interest, got: %#b, want: %#b", got, want)
51+
}
3752
dut.Close(t, listenFd)
3853
if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)}, 1*time.Second); err != nil {
3954
t.Fatalf("expected a RST-ACK packet but got none: %s", err)

0 commit comments

Comments
 (0)