forked from vikshanker/sponge
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtcp_fsm_test_harness.cc
316 lines (284 loc) · 12.7 KB
/
tcp_fsm_test_harness.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
#include "tcp_fsm_test_harness.hh"
#include "tcp_expectation.hh"
#include "tcp_header.hh"
#include "util.hh"
#include <cerrno>
#include <iostream>
#include <stdexcept>
#include <string_view>
#include <sys/socket.h>
#include <utility>
const unsigned int DEFAULT_TEST_WINDOW = 137;
using namespace std;
TestFD::TestFD()
: TestFD([&]() {
int fds[2];
SystemCall("socketpair", socketpair(AF_UNIX, SOCK_SEQPACKET, 0, &fds[0]));
return make_pair(FileDescriptor(fds[0]), TestRFD(fds[1]));
}()) {}
TestFD::TestFD(pair<FileDescriptor, TestRFD> fd_pair)
: FileDescriptor(move(fd_pair.first)), _recv_fd(move(fd_pair.second)) {}
//! \param[in] buffer is the content to write to the TestFD
void TestFD::write(const BufferViewList &buffer) {
auto iovecs = buffer.as_iovecs();
msghdr message{};
message.msg_iov = iovecs.data();
message.msg_iovlen = iovecs.size();
SystemCall("sendmsg", ::sendmsg(fd_num(), &message, MSG_EOR));
}
//! \returns `true` if there is a packet available for reading from TestRFD
bool TestFD::TestRFD::can_read() const {
uint8_t tmp;
return SystemCall("recv", ::recv(fd_num(), &tmp, 1, MSG_PEEK | MSG_DONTWAIT), EAGAIN) >= 0;
}
//! \returns a vector of bytes received on the TestRFD
string TestFD::TestRFD::read() {
string ret(MAX_RECV, 0);
ssize_t ret_read = SystemCall("recv", ::recv(fd_num(), ret.data(), ret.size(), MSG_TRUNC));
if (ret_read > static_cast<ssize_t>(ret.size())) {
throw runtime_error("TestFD unexpectedly got truncated packet.");
}
ret.resize(ret_read);
register_read();
return ret;
}
//! \param[in] seg is the TCPSegment to configure
void TestFdAdapter::config_segment(TCPSegment &seg) {
const auto &cfg = config();
auto &tcp_hdr = seg.header();
tcp_hdr.sport = cfg.source.port();
tcp_hdr.dport = cfg.destination.port();
}
//! \param[in] seg is the TCPSegment to write
void TestFdAdapter::write(TCPSegment &seg) {
config_segment(seg);
TestFD::write(seg.serialize());
}
//! \param[in] seqno is the sequence number of the segment
//! \param[in] ackno is the optional acknowledgment number of the segment; if no value, ACK flag is not set
void TCPTestHarness::send_fin(const WrappingInt32 seqno, const optional<WrappingInt32> ackno) {
SendSegment step{};
if (ackno.has_value()) {
step.with_ack(true).with_ackno(ackno.value());
}
step.with_fin(true).with_seqno(seqno).with_win(DEFAULT_TEST_WINDOW);
execute(step);
}
//! \param[in] seqno is the sequence number of the segment
//! \param[in] ackno is the acknowledgment number of the segment
//! \param[in] swin is the optional window size for the segment; if no value, uses default value (137 bytes)
void TCPTestHarness::send_ack(const WrappingInt32 seqno, const WrappingInt32 ackno, const optional<uint16_t> swin) {
uint16_t win = DEFAULT_TEST_WINDOW;
if (swin.has_value()) {
win = swin.value();
}
execute(SendSegment{}.with_ack(true).with_ackno(ackno).with_seqno(seqno).with_win(win));
}
//! \param[in] seqno is the sequence number of the segment
//! \param[in] ackno is the optional acknowledgment number of the segment; if no value, ACK flag is not set
void TCPTestHarness::send_rst(const WrappingInt32 seqno, const optional<WrappingInt32> ackno) {
SendSegment step{};
if (ackno.has_value()) {
step.with_ack(true).with_ackno(ackno.value());
}
step.with_rst(true).with_seqno(seqno).with_win(DEFAULT_TEST_WINDOW);
execute(step);
}
//! \param[in] seqno is the sequence number of the segment
//! \param[in] ackno is the optional acknowledgment number of the segment; if no value, ACK flag is not set
void TCPTestHarness::send_syn(const WrappingInt32 seqno, const optional<WrappingInt32> ackno) {
SendSegment step{};
if (ackno.has_value()) {
step.with_ack(true).with_ackno(ackno.value());
}
step.with_syn(true).with_seqno(seqno).with_win(DEFAULT_TEST_WINDOW);
execute(step);
}
//! \param[in] seqno is the sequence number of the segment
//! \param[in] ackno is the optional acknowledgment number of the segment; if no value, ACK flag is not set
//! \param[in] val is the value of the one-byte payload
void TCPTestHarness::send_byte(const WrappingInt32 seqno, const optional<WrappingInt32> ackno, const uint8_t val) {
SendSegment step{};
if (ackno.has_value()) {
step.with_ack(true).with_ackno(ackno.value());
}
step.with_payload_size(1).with_data(string{&val, &val + 1}).with_seqno(seqno).with_win(DEFAULT_TEST_WINDOW);
execute(step);
}
//! \param[in] seqno is the sequence number of the segment
//! \param[in] ackno is the acknowledgment number of the segment
//! \param[in] begin is an iterator to the start of the payload
//! \param[in] end is an iterator to the end of the payload
void TCPTestHarness::send_data(const WrappingInt32 seqno, const WrappingInt32 ackno, VecIterT begin, VecIterT end) {
SendSegment step{};
execute(SendSegment{}
.with_ack(true)
.with_ackno(ackno)
.with_payload_size(1)
.with_data(string{begin, end})
.with_seqno(seqno)
.with_win(DEFAULT_TEST_WINDOW));
}
void TCPTestHarness::execute(const TCPTestStep &step, std::string note) {
try {
step.execute(*this);
while (not _fsm.segments_out().empty()) {
_flt.write(_fsm.segments_out().front());
_fsm.segments_out().pop();
}
_steps_executed.emplace_back(step.to_string());
} catch (const TCPExpectationViolation &e) {
cerr << "Test Failure on expectation:\n\t" << step.to_string();
cerr << "\n\nFailure message:\n\t" << e.what();
cerr << "\n\nList of steps that executed successfully:";
for (const string &s : _steps_executed) {
cerr << "\n\t" << s;
}
cerr << endl << endl;
if (note.size() > 0) {
cerr << "Note:\n\t" << note << endl << endl;
}
throw e;
}
}
TCPSegment TCPTestHarness::expect_seg(const ExpectSegment &expectation, std::string note) {
try {
auto ret = expectation.expect_seg(*this);
_steps_executed.emplace_back(expectation.to_string());
return ret;
} catch (const TCPExpectationViolation &e) {
cerr << "Test Failure on expectation:\n\t" << expectation.description() << "\nFailure message:\n\t" << e.what();
cerr << "\n\nList of steps that executed successfully:";
for (const string &s : _steps_executed) {
cerr << "\n\t" << s;
}
cerr << endl << endl;
if (note.size() > 0) {
cerr << "Note:\n\t" << note << endl << endl;
}
throw e;
}
}
//! Create a FSM in the "LISTEN" state.
TCPTestHarness TCPTestHarness::in_listen(const TCPConfig &cfg) {
TCPTestHarness h{cfg};
h.execute(Listen{});
return h;
}
//! \brief Create an FSM which has sent a SYN.
//! \details The SYN has been consumed, but not ACK'd by the test harness
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
TCPTestHarness TCPTestHarness::in_syn_sent(const TCPConfig &cfg, const WrappingInt32 tx_isn) {
TCPConfig c{cfg};
c.fixed_isn = tx_isn;
TCPTestHarness h{c};
h.execute(Connect{});
h.execute(ExpectOneSegment{}.with_no_flags().with_syn(true).with_seqno(tx_isn).with_payload_size(0));
return h;
}
//! \brief Create an FSM with an established connection
//! \details The mahine has sent and received a SYN, and both SYNs have been ACK'd
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
//! \param[in] rx_isn is the ISN of the FSM's inbound sequence. i.e. the
//! seqno for the SYN.
TCPTestHarness TCPTestHarness::in_established(const TCPConfig &cfg,
const WrappingInt32 tx_isn,
const WrappingInt32 rx_isn) {
TCPTestHarness h = in_syn_sent(cfg, tx_isn);
// It has sent a SYN with nothing else, and that SYN has been consumed
// We reply with ACK and SYN.
h.send_syn(rx_isn, tx_isn + 1);
h.execute(ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(rx_isn + 1).with_payload_size(0));
return h;
}
//! \brief Create an FSM in CLOSE_WAIT
//! \details SYNs have been traded, and then the machine received and ACK'd FIN.
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
//! \param[in] rx_isn is the ISN of the FSM's inbound sequence. i.e. the
//! seqno for the SYN.
TCPTestHarness TCPTestHarness::in_close_wait(const TCPConfig &cfg,
const WrappingInt32 tx_isn,
const WrappingInt32 rx_isn) {
TCPTestHarness h = in_established(cfg, tx_isn, rx_isn);
h.send_fin(rx_isn + 1, tx_isn + 1);
h.execute(ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(rx_isn + 2));
return h;
}
//! \brief Create an FSM in LAST_ACK
//! \details SYNs have been traded, then the machine received and ACK'd FIN, and then it sent its own FIN.
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
//! \param[in] rx_isn is the ISN of the FSM's inbound sequence. i.e. the
//! seqno for the SYN.
TCPTestHarness TCPTestHarness::in_last_ack(const TCPConfig &cfg,
const WrappingInt32 tx_isn,
const WrappingInt32 rx_isn) {
TCPTestHarness h = in_close_wait(cfg, tx_isn, rx_isn);
h.execute(Close{});
h.execute(
ExpectOneSegment{}.with_no_flags().with_fin(true).with_ack(true).with_seqno(tx_isn + 1).with_ackno(rx_isn + 2));
return h;
}
//! \brief Create an FSM in FIN_WAIT
//! \details SYNs have been traded, then the TCP sent FIN.
//! No payload was exchanged.
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
//! \param[in] rx_isn is the ISN of the FSM's inbound sequence. i.e. the
//! seqno for the SYN.
TCPTestHarness TCPTestHarness::in_fin_wait_1(const TCPConfig &cfg,
const WrappingInt32 tx_isn,
const WrappingInt32 rx_isn) {
TCPTestHarness h = in_established(cfg, tx_isn, rx_isn);
h.execute(Close{});
h.execute(
ExpectOneSegment{}.with_no_flags().with_fin(true).with_ack(true).with_ackno(rx_isn + 1).with_seqno(tx_isn + 1));
return h;
}
//! \brief Create an FSM in FIN_WAIT_2
//! \details SYNs have been traded, then the TCP sent FIN, which was ACK'd
//! No payload was exchanged.
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
//! \param[in] rx_isn is the ISN of the FSM's inbound sequence. i.e. the
//! seqno for the SYN.
TCPTestHarness TCPTestHarness::in_fin_wait_2(const TCPConfig &cfg,
const WrappingInt32 tx_isn,
const WrappingInt32 rx_isn) {
TCPTestHarness h = in_fin_wait_1(cfg, tx_isn, rx_isn);
h.send_ack(rx_isn + 1, tx_isn + 2);
return h;
}
//! \brief Create an FSM in CLOSING
//! \details SYNs have been traded, then the TCP sent FIN, then received FIN
//! No payload was exchanged.
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
//! \param[in] rx_isn is the ISN of the FSM's inbound sequence. i.e. the
//! seqno for the SYN.
TCPTestHarness TCPTestHarness::in_closing(const TCPConfig &cfg,
const WrappingInt32 tx_isn,
const WrappingInt32 rx_isn) {
TCPTestHarness h = in_fin_wait_1(cfg, tx_isn, rx_isn);
h.send_fin(rx_isn + 1, tx_isn + 1);
h.execute(ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(rx_isn + 2));
return h;
}
//! \brief Create an FSM in TIME_WAIT
//! \details SYNs have been traded, then the TCP sent FIN, then received FIN/ACK, and ACK'd.
//! No payload was exchanged.
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
//! \param[in] rx_isn is the ISN of the FSM's inbound sequence. i.e. the
//! seqno for the SYN.
TCPTestHarness TCPTestHarness::in_time_wait(const TCPConfig &cfg,
const WrappingInt32 tx_isn,
const WrappingInt32 rx_isn) {
TCPTestHarness h = in_fin_wait_1(cfg, tx_isn, rx_isn);
h.send_fin(rx_isn + 1, tx_isn + 2);
h.execute(ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(rx_isn + 2));
return h;
}