CS144 Lab checkpoint 4 starter code
This commit is contained in:
parent
e70095b9ff
commit
9d00589b3c
@ -1 +1,9 @@
|
|||||||
|
add_library (stream_copy STATIC bidirectional_stream_copy.cc)
|
||||||
|
|
||||||
|
add_sponge_exec (udp_tcpdump ${LIBPCAP})
|
||||||
|
add_sponge_exec (tcp_native stream_copy)
|
||||||
|
add_sponge_exec (tun)
|
||||||
|
add_sponge_exec (tcp_udp stream_copy)
|
||||||
|
add_sponge_exec (tcp_ipv4 stream_copy)
|
||||||
add_sponge_exec (webget)
|
add_sponge_exec (webget)
|
||||||
|
add_sponge_exec (tcp_benchmark)
|
||||||
|
|||||||
91
apps/bidirectional_stream_copy.cc
Normal file
91
apps/bidirectional_stream_copy.cc
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
#include "bidirectional_stream_copy.hh"
|
||||||
|
|
||||||
|
#include "byte_stream.hh"
|
||||||
|
#include "eventloop.hh"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
void bidirectional_stream_copy(Socket &socket) {
|
||||||
|
constexpr size_t max_copy_length = 65536;
|
||||||
|
constexpr size_t buffer_size = 1048576;
|
||||||
|
|
||||||
|
EventLoop _eventloop{};
|
||||||
|
FileDescriptor _input{STDIN_FILENO};
|
||||||
|
FileDescriptor _output{STDOUT_FILENO};
|
||||||
|
ByteStream _outbound{buffer_size};
|
||||||
|
ByteStream _inbound{buffer_size};
|
||||||
|
bool _outbound_shutdown{false};
|
||||||
|
bool _inbound_shutdown{false};
|
||||||
|
|
||||||
|
socket.set_blocking(false);
|
||||||
|
_input.set_blocking(false);
|
||||||
|
_output.set_blocking(false);
|
||||||
|
|
||||||
|
// rule 1: read from stdin into outbound byte stream
|
||||||
|
_eventloop.add_rule(
|
||||||
|
_input,
|
||||||
|
Direction::In,
|
||||||
|
[&] {
|
||||||
|
_outbound.write(_input.read(_outbound.remaining_capacity()));
|
||||||
|
if (_input.eof()) {
|
||||||
|
_outbound.end_input();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[&] { return (not _outbound.error()) and (_outbound.remaining_capacity() > 0) and (not _inbound.error()); },
|
||||||
|
[&] { _outbound.end_input(); });
|
||||||
|
|
||||||
|
// rule 2: read from outbound byte stream into socket
|
||||||
|
_eventloop.add_rule(socket,
|
||||||
|
Direction::Out,
|
||||||
|
[&] {
|
||||||
|
const size_t bytes_to_write = min(max_copy_length, _outbound.buffer_size());
|
||||||
|
const size_t bytes_written = socket.write(_outbound.peek_output(bytes_to_write), false);
|
||||||
|
_outbound.pop_output(bytes_written);
|
||||||
|
if (_outbound.eof()) {
|
||||||
|
socket.shutdown(SHUT_WR);
|
||||||
|
_outbound_shutdown = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[&] { return (not _outbound.buffer_empty()) or (_outbound.eof() and not _outbound_shutdown); },
|
||||||
|
[&] { _outbound.end_input(); });
|
||||||
|
|
||||||
|
// rule 3: read from socket into inbound byte stream
|
||||||
|
_eventloop.add_rule(
|
||||||
|
socket,
|
||||||
|
Direction::In,
|
||||||
|
[&] {
|
||||||
|
_inbound.write(socket.read(_inbound.remaining_capacity()));
|
||||||
|
if (socket.eof()) {
|
||||||
|
_inbound.end_input();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[&] { return (not _inbound.error()) and (_inbound.remaining_capacity() > 0) and (not _outbound.error()); },
|
||||||
|
[&] { _inbound.end_input(); });
|
||||||
|
|
||||||
|
// rule 4: read from inbound byte stream into stdout
|
||||||
|
_eventloop.add_rule(_output,
|
||||||
|
Direction::Out,
|
||||||
|
[&] {
|
||||||
|
const size_t bytes_to_write = min(max_copy_length, _inbound.buffer_size());
|
||||||
|
const size_t bytes_written = _output.write(_inbound.peek_output(bytes_to_write), false);
|
||||||
|
_inbound.pop_output(bytes_written);
|
||||||
|
|
||||||
|
if (_inbound.eof()) {
|
||||||
|
_output.close();
|
||||||
|
_inbound_shutdown = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[&] { return (not _inbound.buffer_empty()) or (_inbound.eof() and not _inbound_shutdown); },
|
||||||
|
[&] { _inbound.end_input(); });
|
||||||
|
|
||||||
|
// loop until completion
|
||||||
|
while (true) {
|
||||||
|
if (EventLoop::Result::Exit == _eventloop.wait_next_event(-1)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
apps/bidirectional_stream_copy.hh
Normal file
9
apps/bidirectional_stream_copy.hh
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#ifndef SPONGE_APPS_BIDIRECTIONAL_STREAM_COPY_HH
|
||||||
|
#define SPONGE_APPS_BIDIRECTIONAL_STREAM_COPY_HH
|
||||||
|
|
||||||
|
#include "socket.hh"
|
||||||
|
|
||||||
|
//! Copy socket input/output to stdin/stdout until finished
|
||||||
|
void bidirectional_stream_copy(Socket &socket);
|
||||||
|
|
||||||
|
#endif // SPONGE_APPS_BIDIRECTIONAL_STREAM_COPY_HH
|
||||||
116
apps/tcp_benchmark.cc
Normal file
116
apps/tcp_benchmark.cc
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
#include "tcp_connection.hh"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
constexpr size_t len = 100 * 1024 * 1024;
|
||||||
|
|
||||||
|
void move_segments(TCPConnection &x, TCPConnection &y, vector<TCPSegment> &segments, const bool reorder) {
|
||||||
|
while (not x.segments_out().empty()) {
|
||||||
|
segments.emplace_back(move(x.segments_out().front()));
|
||||||
|
x.segments_out().pop();
|
||||||
|
}
|
||||||
|
if (reorder) {
|
||||||
|
for (auto it = segments.rbegin(); it != segments.rend(); ++it) {
|
||||||
|
y.segment_received(move(*it));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (auto it = segments.begin(); it != segments.end(); ++it) {
|
||||||
|
y.segment_received(move(*it));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
segments.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void main_loop(const bool reorder) {
|
||||||
|
TCPConfig config;
|
||||||
|
TCPConnection x{config}, y{config};
|
||||||
|
|
||||||
|
string string_to_send(len, 'x');
|
||||||
|
for (auto &ch : string_to_send) {
|
||||||
|
ch = rand();
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer bytes_to_send{string(string_to_send)};
|
||||||
|
x.connect();
|
||||||
|
y.end_input_stream();
|
||||||
|
|
||||||
|
bool x_closed = false;
|
||||||
|
|
||||||
|
string string_received;
|
||||||
|
string_received.reserve(len);
|
||||||
|
|
||||||
|
const auto first_time = high_resolution_clock::now();
|
||||||
|
|
||||||
|
auto loop = [&] {
|
||||||
|
// write input into x
|
||||||
|
while (bytes_to_send.size() and x.remaining_outbound_capacity()) {
|
||||||
|
const auto want = min(x.remaining_outbound_capacity(), bytes_to_send.size());
|
||||||
|
const auto written = x.write(string(bytes_to_send.str().substr(0, want)));
|
||||||
|
if (want != written) {
|
||||||
|
throw runtime_error("want = " + to_string(want) + ", written = " + to_string(written));
|
||||||
|
}
|
||||||
|
bytes_to_send.remove_prefix(written);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes_to_send.size() == 0 and not x_closed) {
|
||||||
|
x.end_input_stream();
|
||||||
|
x_closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// exchange segments between x and y but in reverse order
|
||||||
|
vector<TCPSegment> segments;
|
||||||
|
move_segments(x, y, segments, reorder);
|
||||||
|
move_segments(y, x, segments, false);
|
||||||
|
|
||||||
|
// read output from y
|
||||||
|
const auto available_output = y.inbound_stream().buffer_size();
|
||||||
|
if (available_output > 0) {
|
||||||
|
string_received.append(y.inbound_stream().read(available_output));
|
||||||
|
}
|
||||||
|
|
||||||
|
// time passes
|
||||||
|
x.tick(1000);
|
||||||
|
y.tick(1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
while (not y.inbound_stream().eof()) {
|
||||||
|
loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string_received != string_to_send) {
|
||||||
|
throw runtime_error("strings sent vs. received don't match");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto final_time = high_resolution_clock::now();
|
||||||
|
|
||||||
|
const auto duration = duration_cast<nanoseconds>(final_time - first_time).count();
|
||||||
|
|
||||||
|
const auto gigabits_per_second = len * 8.0 / double(duration);
|
||||||
|
|
||||||
|
cout << fixed << setprecision(2);
|
||||||
|
cout << "CPU-limited throughput" << (reorder ? " with reordering: " : " : ") << gigabits_per_second
|
||||||
|
<< " Gbit/s\n";
|
||||||
|
|
||||||
|
while (x.active() or y.active()) {
|
||||||
|
loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
try {
|
||||||
|
main_loop(false);
|
||||||
|
main_loop(true);
|
||||||
|
} catch (const exception &e) {
|
||||||
|
cerr << e.what() << "\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
162
apps/tcp_ipv4.cc
Normal file
162
apps/tcp_ipv4.cc
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
#include "bidirectional_stream_copy.hh"
|
||||||
|
#include "tcp_config.hh"
|
||||||
|
#include "tcp_sponge_socket.hh"
|
||||||
|
#include "tun.hh"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <random>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
constexpr const char *TUN_DFLT = "tun144";
|
||||||
|
const string LOCAL_ADDRESS_DFLT = "169.254.144.9";
|
||||||
|
|
||||||
|
static void show_usage(const char *argv0, const char *msg) {
|
||||||
|
cout << "Usage: " << argv0 << " [options] <host> <port>\n\n"
|
||||||
|
<< " Option Default\n"
|
||||||
|
<< " -- --\n\n"
|
||||||
|
|
||||||
|
<< " -l Server (listen) mode. (client mode)\n"
|
||||||
|
<< " In server mode, <host>:<port> is the address to bind.\n\n"
|
||||||
|
|
||||||
|
<< " -a <addr> Set source address (client mode only) " << LOCAL_ADDRESS_DFLT << "\n"
|
||||||
|
<< " -s <port> Set source port (client mode only) (random)\n\n"
|
||||||
|
|
||||||
|
<< " -w <winsz> Use a window of <winsz> bytes " << TCPConfig::MAX_PAYLOAD_SIZE
|
||||||
|
<< "\n\n"
|
||||||
|
|
||||||
|
<< " -t <tmout> Set rt_timeout to tmout " << TCPConfig::TIMEOUT_DFLT << "\n\n"
|
||||||
|
|
||||||
|
<< " -d <tundev> Connect to tun <tundev> " << TUN_DFLT << "\n\n"
|
||||||
|
|
||||||
|
<< " -Lu <loss> Set uplink loss to <rate> (float in 0..1) (no loss)\n"
|
||||||
|
<< " -Ld <loss> Set downlink loss to <rate> (float in 0..1) (no loss)\n\n"
|
||||||
|
|
||||||
|
<< " -h Show this message.\n\n";
|
||||||
|
|
||||||
|
if (msg != nullptr) {
|
||||||
|
cout << msg;
|
||||||
|
}
|
||||||
|
cout << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_argc(int argc, char **argv, int curr, const char *err) {
|
||||||
|
if (curr + 3 >= argc) {
|
||||||
|
show_usage(argv[0], err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static tuple<TCPConfig, FdAdapterConfig, bool, char *> get_config(int argc, char **argv) {
|
||||||
|
TCPConfig c_fsm{};
|
||||||
|
FdAdapterConfig c_filt{};
|
||||||
|
char *tundev = nullptr;
|
||||||
|
|
||||||
|
int curr = 1;
|
||||||
|
bool listen = false;
|
||||||
|
|
||||||
|
string source_address = LOCAL_ADDRESS_DFLT;
|
||||||
|
string source_port = to_string(uint16_t(random_device()()));
|
||||||
|
|
||||||
|
while (argc - curr > 2) {
|
||||||
|
if (strncmp("-l", argv[curr], 3) == 0) {
|
||||||
|
listen = true;
|
||||||
|
curr += 1;
|
||||||
|
|
||||||
|
} else if (strncmp("-a", argv[curr], 3) == 0) {
|
||||||
|
check_argc(argc, argv, curr, "ERROR: -a requires one argument.");
|
||||||
|
source_address = argv[curr + 1];
|
||||||
|
curr += 2;
|
||||||
|
|
||||||
|
} else if (strncmp("-s", argv[curr], 3) == 0) {
|
||||||
|
check_argc(argc, argv, curr, "ERROR: -s requires one argument.");
|
||||||
|
source_port = argv[curr + 1];
|
||||||
|
curr += 2;
|
||||||
|
|
||||||
|
} else if (strncmp("-w", argv[curr], 3) == 0) {
|
||||||
|
check_argc(argc, argv, curr, "ERROR: -w requires one argument.");
|
||||||
|
c_fsm.recv_capacity = strtol(argv[curr + 1], nullptr, 0);
|
||||||
|
curr += 2;
|
||||||
|
|
||||||
|
} else if (strncmp("-t", argv[curr], 3) == 0) {
|
||||||
|
check_argc(argc, argv, curr, "ERROR: -t requires one argument.");
|
||||||
|
c_fsm.rt_timeout = strtol(argv[curr + 1], nullptr, 0);
|
||||||
|
curr += 2;
|
||||||
|
|
||||||
|
} else if (strncmp("-d", argv[curr], 3) == 0) {
|
||||||
|
check_argc(argc, argv, curr, "ERROR: -t requires one argument.");
|
||||||
|
tundev = argv[curr + 1];
|
||||||
|
curr += 2;
|
||||||
|
|
||||||
|
} else if (strncmp("-Lu", argv[curr], 3) == 0) {
|
||||||
|
check_argc(argc, argv, curr, "ERROR: -Lu requires one argument.");
|
||||||
|
float lossrate = strtof(argv[curr + 1], nullptr);
|
||||||
|
using LossRateUpT = decltype(c_filt.loss_rate_up);
|
||||||
|
c_filt.loss_rate_up =
|
||||||
|
static_cast<LossRateUpT>(static_cast<float>(numeric_limits<LossRateUpT>::max()) * lossrate);
|
||||||
|
curr += 2;
|
||||||
|
|
||||||
|
} else if (strncmp("-Ld", argv[curr], 3) == 0) {
|
||||||
|
check_argc(argc, argv, curr, "ERROR: -Lu requires one argument.");
|
||||||
|
float lossrate = strtof(argv[curr + 1], nullptr);
|
||||||
|
using LossRateDnT = decltype(c_filt.loss_rate_dn);
|
||||||
|
c_filt.loss_rate_dn =
|
||||||
|
static_cast<LossRateDnT>(static_cast<float>(numeric_limits<LossRateDnT>::max()) * lossrate);
|
||||||
|
curr += 2;
|
||||||
|
|
||||||
|
} else if (strncmp("-h", argv[curr], 3) == 0) {
|
||||||
|
show_usage(argv[0], nullptr);
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
show_usage(argv[0], string("ERROR: unrecognized option " + string(argv[curr])).c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse positional command-line arguments
|
||||||
|
if (listen) {
|
||||||
|
c_filt.source = {"0", argv[curr + 1]};
|
||||||
|
if (c_filt.source.port() == 0) {
|
||||||
|
show_usage(argv[0], "ERROR: listen port cannot be zero in server mode.");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c_filt.destination = {argv[curr], argv[curr + 1]};
|
||||||
|
c_filt.source = {source_address, source_port};
|
||||||
|
}
|
||||||
|
|
||||||
|
return make_tuple(c_fsm, c_filt, listen, tundev);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
try {
|
||||||
|
if (argc < 3) {
|
||||||
|
show_usage(argv[0], "ERROR: required arguments are missing.");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [c_fsm, c_filt, listen, tun_dev_name] = get_config(argc, argv);
|
||||||
|
LossyTCPOverIPv4SpongeSocket tcp_socket(LossyTCPOverIPv4OverTunFdAdapter(
|
||||||
|
TCPOverIPv4OverTunFdAdapter(TunFD(tun_dev_name == nullptr ? TUN_DFLT : tun_dev_name))));
|
||||||
|
|
||||||
|
if (listen) {
|
||||||
|
tcp_socket.listen_and_accept(c_fsm, c_filt);
|
||||||
|
} else {
|
||||||
|
tcp_socket.connect(c_fsm, c_filt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bidirectional_stream_copy(tcp_socket);
|
||||||
|
tcp_socket.wait_until_closed();
|
||||||
|
} catch (const exception &e) {
|
||||||
|
cerr << "Exception: " << e.what() << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
43
apps/tcp_native.cc
Normal file
43
apps/tcp_native.cc
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#include "bidirectional_stream_copy.hh"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
void show_usage(const char *argv0) {
|
||||||
|
cerr << "Usage: " << argv0 << " [-l] <host> <port>\n\n"
|
||||||
|
<< " -l specifies listen mode; <host>:<port> is the listening address." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
try {
|
||||||
|
bool server_mode = false;
|
||||||
|
if (argc < 3 || ((server_mode = (strncmp("-l", argv[1], 3) == 0)) && argc < 4)) {
|
||||||
|
show_usage(argv[0]);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// in client mode, connect; in server mode, accept exactly one connection
|
||||||
|
auto socket = [&] {
|
||||||
|
if (server_mode) {
|
||||||
|
TCPSocket listening_socket; // create a TCP socket
|
||||||
|
listening_socket.set_reuseaddr(); // reuse the server's address as soon as the program quits
|
||||||
|
listening_socket.bind({argv[2], argv[3]}); // bind to specified address
|
||||||
|
listening_socket.listen(); // mark the socket as listening for incoming connections
|
||||||
|
return listening_socket.accept(); // accept exactly one connection
|
||||||
|
}
|
||||||
|
TCPSocket connecting_socket;
|
||||||
|
connecting_socket.connect({argv[1], argv[2]});
|
||||||
|
return connecting_socket;
|
||||||
|
}();
|
||||||
|
|
||||||
|
bidirectional_stream_copy(socket);
|
||||||
|
} catch (const exception &e) {
|
||||||
|
cerr << "Exception: " << e.what() << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
136
apps/tcp_udp.cc
Normal file
136
apps/tcp_udp.cc
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
#include "bidirectional_stream_copy.hh"
|
||||||
|
#include "tcp_config.hh"
|
||||||
|
#include "tcp_sponge_socket.hh"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <random>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
constexpr uint16_t DPORT_DFLT = 1440;
|
||||||
|
|
||||||
|
static void show_usage(const char *argv0, const char *msg) {
|
||||||
|
cout << "Usage: " << argv0 << " [options] <host> <port>\n\n"
|
||||||
|
|
||||||
|
<< " Option Default\n"
|
||||||
|
<< " -- --\n\n"
|
||||||
|
|
||||||
|
<< " -l Server (listen) mode. (client mode)\n"
|
||||||
|
<< " In server mode, <host>:<port> is the address to bind.\n\n"
|
||||||
|
|
||||||
|
<< " -w <winsz> Use a window of <winsz> bytes " << TCPConfig::MAX_PAYLOAD_SIZE
|
||||||
|
<< "\n\n"
|
||||||
|
|
||||||
|
<< " -t <tmout> Set rt_timeout to tmout " << TCPConfig::TIMEOUT_DFLT << "\n\n"
|
||||||
|
|
||||||
|
<< " -Lu <loss> Set uplink loss to <rate> (float in 0..1) (no loss)\n"
|
||||||
|
<< " -Ld <loss> Set downlink loss to <rate> (float in 0..1) (no loss)\n\n"
|
||||||
|
|
||||||
|
<< " -h Show this message and quit.\n\n";
|
||||||
|
|
||||||
|
if (msg != nullptr) {
|
||||||
|
cout << msg;
|
||||||
|
}
|
||||||
|
cout << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_argc(int argc, char **argv, int curr, const char *err) {
|
||||||
|
if (curr + 3 >= argc) {
|
||||||
|
show_usage(argv[0], err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static tuple<TCPConfig, FdAdapterConfig, bool> get_config(int argc, char **argv) {
|
||||||
|
TCPConfig c_fsm{};
|
||||||
|
FdAdapterConfig c_filt{};
|
||||||
|
|
||||||
|
int curr = 1;
|
||||||
|
bool listen = false;
|
||||||
|
|
||||||
|
while (argc - curr > 2) {
|
||||||
|
if (strncmp("-l", argv[curr], 3) == 0) {
|
||||||
|
listen = true;
|
||||||
|
curr += 1;
|
||||||
|
|
||||||
|
} else if (strncmp("-w", argv[curr], 3) == 0) {
|
||||||
|
check_argc(argc, argv, curr, "ERROR: -w requires one argument.");
|
||||||
|
c_fsm.recv_capacity = strtol(argv[curr + 1], nullptr, 0);
|
||||||
|
curr += 2;
|
||||||
|
|
||||||
|
} else if (strncmp("-t", argv[curr], 3) == 0) {
|
||||||
|
check_argc(argc, argv, curr, "ERROR: -t requires one argument.");
|
||||||
|
c_fsm.rt_timeout = strtol(argv[curr + 1], nullptr, 0);
|
||||||
|
curr += 2;
|
||||||
|
|
||||||
|
} else if (strncmp("-Lu", argv[curr], 3) == 0) {
|
||||||
|
check_argc(argc, argv, curr, "ERROR: -Lu requires one argument.");
|
||||||
|
float lossrate = strtof(argv[curr + 1], nullptr);
|
||||||
|
using LossRateUpT = decltype(c_filt.loss_rate_up);
|
||||||
|
c_filt.loss_rate_up =
|
||||||
|
static_cast<LossRateUpT>(static_cast<float>(numeric_limits<LossRateUpT>::max()) * lossrate);
|
||||||
|
curr += 2;
|
||||||
|
|
||||||
|
} else if (strncmp("-Ld", argv[curr], 3) == 0) {
|
||||||
|
check_argc(argc, argv, curr, "ERROR: -Lu requires one argument.");
|
||||||
|
float lossrate = strtof(argv[curr + 1], nullptr);
|
||||||
|
using LossRateDnT = decltype(c_filt.loss_rate_dn);
|
||||||
|
c_filt.loss_rate_dn =
|
||||||
|
static_cast<LossRateDnT>(static_cast<float>(numeric_limits<LossRateDnT>::max()) * lossrate);
|
||||||
|
curr += 2;
|
||||||
|
|
||||||
|
} else if (strncmp("-h", argv[curr], 3) == 0) {
|
||||||
|
show_usage(argv[0], nullptr);
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
show_usage(argv[0], std::string("ERROR: unrecognized option " + std::string(argv[curr])).c_str());
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen) {
|
||||||
|
c_filt.source = {"0", argv[argc - 1]};
|
||||||
|
} else {
|
||||||
|
c_filt.destination = {argv[argc - 2], argv[argc - 1]};
|
||||||
|
}
|
||||||
|
|
||||||
|
return make_tuple(c_fsm, c_filt, listen);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
try {
|
||||||
|
if (argc < 3) {
|
||||||
|
show_usage(argv[0], "ERROR: required arguments are missing.");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle configuration and UDP setup from cmdline arguments
|
||||||
|
auto [c_fsm, c_filt, listen] = get_config(argc, argv);
|
||||||
|
|
||||||
|
// build a TCP FSM on top of the UDP socket
|
||||||
|
UDPSocket udp_sock;
|
||||||
|
if (listen) {
|
||||||
|
udp_sock.bind(c_filt.source);
|
||||||
|
}
|
||||||
|
LossyTCPOverUDPSpongeSocket tcp_socket(LossyTCPOverUDPSocketAdapter(TCPOverUDPSocketAdapter(move(udp_sock))));
|
||||||
|
if (listen) {
|
||||||
|
tcp_socket.listen_and_accept(c_fsm, c_filt);
|
||||||
|
} else {
|
||||||
|
tcp_socket.connect(c_fsm, c_filt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bidirectional_stream_copy(tcp_socket);
|
||||||
|
tcp_socket.wait_until_closed();
|
||||||
|
} catch (const exception &e) {
|
||||||
|
cerr << "Exception: " << e.what() << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
57
apps/tun.cc
Normal file
57
apps/tun.cc
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#include "tun.hh"
|
||||||
|
|
||||||
|
#include "ipv4_datagram.hh"
|
||||||
|
#include "parser.hh"
|
||||||
|
#include "tcp_segment.hh"
|
||||||
|
#include "util.hh"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <exception>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
try {
|
||||||
|
TunFD tun("tun144");
|
||||||
|
while (true) {
|
||||||
|
auto buffer = tun.read();
|
||||||
|
cout << "\n\n***\n*** Got packet:\n***\n";
|
||||||
|
hexdump(buffer.data(), buffer.size());
|
||||||
|
|
||||||
|
IPv4Datagram ip_dgram;
|
||||||
|
|
||||||
|
cout << "attempting to parse as ipv4 datagram... ";
|
||||||
|
if (ip_dgram.parse(move(buffer)) != ParseResult::NoError) {
|
||||||
|
cout << "failed.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "success! totlen=" << ip_dgram.header().len << ", IPv4 header contents:\n";
|
||||||
|
cout << ip_dgram.header().to_string();
|
||||||
|
|
||||||
|
if (ip_dgram.header().proto != IPv4Header::PROTO_TCP) {
|
||||||
|
cout << "\nNot TCP, skipping.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "\nAttempting to parse as a TCP segment... ";
|
||||||
|
|
||||||
|
TCPSegment tcp_seg;
|
||||||
|
|
||||||
|
if (tcp_seg.parse(ip_dgram.payload(), ip_dgram.header().pseudo_cksum()) != ParseResult::NoError) {
|
||||||
|
cout << "failed.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "success! payload len=" << tcp_seg.payload().size() << ", TCP header contents:\n";
|
||||||
|
cout << tcp_seg.header().to_string() << endl;
|
||||||
|
}
|
||||||
|
} catch (const exception &e) {
|
||||||
|
cout << "Exception: " << e.what() << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
293
apps/udp_tcpdump.cc
Normal file
293
apps/udp_tcpdump.cc
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
#include "parser.hh"
|
||||||
|
#include "tcp_header.hh"
|
||||||
|
#include "tcp_segment.hh"
|
||||||
|
#include "util.hh"
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <pcap/pcap.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
static void show_usage(const char *arg0, const char *errmsg) {
|
||||||
|
cout << "Usage: " << arg0 << " [-i <intf>] [-F <file>] [-h|--help] <expression>\n\n"
|
||||||
|
<< " -i <intf> only capture packets from <intf> (default: all)\n\n"
|
||||||
|
|
||||||
|
<< " -F <file> reads in a filter expression from <file>\n"
|
||||||
|
<< " <expression> is ignored if -F is supplied.\n\n"
|
||||||
|
|
||||||
|
<< " -h, --help show this message\n\n"
|
||||||
|
<< " <expression> a filter expression in pcap-filter(7) syntax\n";
|
||||||
|
|
||||||
|
if (errmsg != nullptr) {
|
||||||
|
cout << '\n' << errmsg;
|
||||||
|
}
|
||||||
|
cout << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_arg(char *arg0, int argc, int curr, const char *errmsg) {
|
||||||
|
if (curr + 1 >= argc) {
|
||||||
|
show_usage(arg0, errmsg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_arguments(int argc, char **argv, char **dev_ptr) {
|
||||||
|
int curr = 1;
|
||||||
|
while (curr < argc) {
|
||||||
|
if (strncmp("-i", argv[curr], 3) == 0) {
|
||||||
|
check_arg(argv[0], argc, curr, "ERROR: -i requires an argument");
|
||||||
|
*dev_ptr = argv[curr + 1];
|
||||||
|
curr += 2;
|
||||||
|
|
||||||
|
} else if ((strncmp("-h", argv[curr], 3) == 0) || (strncmp("--help", argv[curr], 7) == 0)) {
|
||||||
|
show_usage(argv[0], nullptr);
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return curr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string inet4_addr(const uint8_t *data) {
|
||||||
|
char addrbuf[128];
|
||||||
|
auto *addr = reinterpret_cast<const in_addr *>(data);
|
||||||
|
if (inet_ntop(AF_INET, addr, static_cast<char *>(addrbuf), 128) == nullptr) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
return string(static_cast<char *>(addrbuf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static string inet6_addr(const uint8_t *data) {
|
||||||
|
char addrbuf[128];
|
||||||
|
auto *addr = reinterpret_cast<const in6_addr *>(data);
|
||||||
|
if (inet_ntop(AF_INET6, addr, static_cast<char *>(addrbuf), 128) == nullptr) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
return string(static_cast<char *>(addrbuf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int process_ipv4_ipv6(int len, const uint8_t *data, string &src_addr, string &dst_addr) {
|
||||||
|
// this is either an IPv4 or IPv6 packet, we hope
|
||||||
|
if (len < 1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int data_offset = 0;
|
||||||
|
const uint8_t pt = data[0] & 0xf0;
|
||||||
|
if (pt == 0x40) {
|
||||||
|
// check packet length and proto
|
||||||
|
data_offset = (data[0] & 0x0f) * 4;
|
||||||
|
if (len < data_offset) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (data[9] != 0x11) {
|
||||||
|
cerr << "Not UDP; ";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
src_addr = inet4_addr(data + 12);
|
||||||
|
dst_addr = inet4_addr(data + 16);
|
||||||
|
} else if (pt == 0x60) {
|
||||||
|
// check packet length
|
||||||
|
if (len < 42) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
data_offset = 40;
|
||||||
|
uint8_t nxt = data[6];
|
||||||
|
while (nxt != 0x11) {
|
||||||
|
if (nxt != 0 && nxt != 43 && nxt != 60) {
|
||||||
|
cerr << "Not UDP or fragmented; ";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
nxt = data[data_offset];
|
||||||
|
data_offset += 8 * (1 + data[data_offset + 1]);
|
||||||
|
if (len < data_offset + 2) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
src_addr = inet6_addr(data + 8);
|
||||||
|
dst_addr = inet6_addr(data + 24);
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data_offset + 8; // skip UDP header
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
char *dev = nullptr;
|
||||||
|
const int exp_start = parse_arguments(argc, argv, &dev);
|
||||||
|
|
||||||
|
// create pcap handle
|
||||||
|
if (dev != nullptr) {
|
||||||
|
cout << "Capturing on interface " << dev;
|
||||||
|
} else {
|
||||||
|
cout << "Capturing on all interfaces";
|
||||||
|
}
|
||||||
|
pcap_t *p_hdl = nullptr;
|
||||||
|
const int dl_type = [&] {
|
||||||
|
char errbuf[PCAP_ERRBUF_SIZE] = {
|
||||||
|
0,
|
||||||
|
};
|
||||||
|
p_hdl = pcap_open_live(dev, 65535, 0, 100, static_cast<char *>(errbuf));
|
||||||
|
if (p_hdl == nullptr) {
|
||||||
|
cout << "\nError initiating capture: " << static_cast<char *>(errbuf) << endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
int dlt = pcap_datalink(p_hdl);
|
||||||
|
// need to handle: DLT_RAW, DLT_NULL, DLT_EN10MB, DLT_LINUX_SLL
|
||||||
|
if (dlt != DLT_RAW && dlt != DLT_NULL && dlt != DLT_EN10MB && dlt != DLT_LINUX_SLL
|
||||||
|
#ifdef DLT_LINUX_SLL2
|
||||||
|
&& dlt != DLT_LINUX_SLL2
|
||||||
|
#endif
|
||||||
|
) {
|
||||||
|
cout << "\nError: unsupported datalink type " << pcap_datalink_val_to_description(dlt) << endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
cout << " (type: " << pcap_datalink_val_to_description(dlt) << ")\n";
|
||||||
|
return dlt;
|
||||||
|
}();
|
||||||
|
|
||||||
|
// compile and set filter
|
||||||
|
{
|
||||||
|
struct bpf_program p_flt {};
|
||||||
|
stringstream f_stream;
|
||||||
|
for (int i = exp_start; i < argc; ++i) {
|
||||||
|
f_stream << argv[i] << ' ';
|
||||||
|
}
|
||||||
|
string filter_expression = f_stream.str();
|
||||||
|
cout << "Using filter expression: " << filter_expression << "\n";
|
||||||
|
if (pcap_compile(p_hdl, &p_flt, filter_expression.c_str(), 1, PCAP_NETMASK_UNKNOWN) != 0) {
|
||||||
|
cout << "Error compiling filter expression: " << pcap_geterr(p_hdl) << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (pcap_setfilter(p_hdl, &p_flt) != 0) {
|
||||||
|
cout << "Error configuring packet filter: " << pcap_geterr(p_hdl) << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
pcap_freecode(&p_flt);
|
||||||
|
}
|
||||||
|
|
||||||
|
int next_ret = 0;
|
||||||
|
struct pcap_pkthdr *pkt_hdr = nullptr;
|
||||||
|
const uint8_t *pkt_data = nullptr;
|
||||||
|
cout << setfill('0');
|
||||||
|
while ((next_ret = pcap_next_ex(p_hdl, &pkt_hdr, &pkt_data)) >= 0) {
|
||||||
|
if (next_ret == 0) {
|
||||||
|
// timeout; just listen again
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t hdr_off = 0;
|
||||||
|
int start_off = 0;
|
||||||
|
// figure out where in the datagram to look based on link type
|
||||||
|
if (dl_type == DLT_NULL) {
|
||||||
|
hdr_off = 4;
|
||||||
|
if (pkt_hdr->caplen < hdr_off) {
|
||||||
|
cerr << "[INFO] Skipping malformed packet.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const uint8_t pt = pkt_data[3];
|
||||||
|
if (pt != 2 && pt != 24 && pt != 28 && pt != 30) {
|
||||||
|
cerr << "[INFO] Skipping non-IP packet.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (dl_type == DLT_EN10MB) {
|
||||||
|
hdr_off = 14;
|
||||||
|
if (pkt_hdr->caplen < hdr_off) {
|
||||||
|
cerr << "[INFO] Skipping malformed packet.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const uint16_t pt = (pkt_data[12] << 8) | pkt_data[13];
|
||||||
|
if (pt != 0x0800 && pt != 0x86dd) {
|
||||||
|
cerr << "[INFO] Skipping non-IP packet.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (dl_type == DLT_LINUX_SLL) {
|
||||||
|
hdr_off = 16;
|
||||||
|
if (pkt_hdr->caplen < hdr_off) {
|
||||||
|
cerr << "[INFO] Skipping malformed packet.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const uint16_t pt = (pkt_data[14] << 8) | pkt_data[15];
|
||||||
|
if (pt != 0x0800 && pt != 0x86dd) {
|
||||||
|
cerr << "[INFO] Skipping non-IP packet.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#ifdef DLT_LINUX_SLL2
|
||||||
|
} else if (dl_type == DLT_LINUX_SLL2) {
|
||||||
|
if (pkt_hdr->caplen < 20) {
|
||||||
|
cerr << "[INFO] Skipping malformed packet.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const uint16_t pt = (pkt_data[0] << 8) | pkt_data[1];
|
||||||
|
hdr_off = 20;
|
||||||
|
if (pt != 0x0800 && pt != 0x86dd) {
|
||||||
|
cerr << "[INFO] Skipping non-IP packet.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} else if (dl_type != DLT_RAW) {
|
||||||
|
cerr << "Mysterious datalink type. Giving up.";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now actually parse the packet
|
||||||
|
string src{}, dst{};
|
||||||
|
if ((start_off = process_ipv4_ipv6(pkt_hdr->caplen - hdr_off, pkt_data + hdr_off, src, dst)) < 0) {
|
||||||
|
cerr << "Error parsing IPv4/IPv6 packet. Skipping.\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hdr_off + start_off is now the start of the UDP payload
|
||||||
|
const size_t payload_off = hdr_off + start_off;
|
||||||
|
const size_t payload_len = pkt_hdr->caplen - payload_off;
|
||||||
|
|
||||||
|
string_view payload{reinterpret_cast<const char *>(pkt_data) + payload_off, payload_len};
|
||||||
|
|
||||||
|
// try to parse UDP payload as TCP packet
|
||||||
|
auto seg = TCPSegment{};
|
||||||
|
if (const auto res = seg.parse(string(payload), 0); res > ParseResult::BadChecksum) {
|
||||||
|
cout << "(did not recognize TCP header) src: " << src << " dst: " << dst << '\n';
|
||||||
|
} else {
|
||||||
|
const TCPHeader &tcp_hdr = seg.header();
|
||||||
|
uint32_t seqlen = seg.length_in_sequence_space();
|
||||||
|
|
||||||
|
cout << src << ':' << tcp_hdr.sport << " > " << dst << ':' << tcp_hdr.dport << "\n Flags ["
|
||||||
|
|
||||||
|
<< (tcp_hdr.urg ? "U" : "") << (tcp_hdr.psh ? "P" : "") << (tcp_hdr.rst ? "R" : "")
|
||||||
|
<< (tcp_hdr.syn ? "S" : "") << (tcp_hdr.fin ? "F" : "") << (tcp_hdr.ack ? "." : "")
|
||||||
|
|
||||||
|
<< "] cksum 0x" << hex << setw(4) << tcp_hdr.cksum << dec
|
||||||
|
<< (res == ParseResult::NoError ? " (correct)" : " (incorrect!)")
|
||||||
|
|
||||||
|
<< " seq " << tcp_hdr.seqno;
|
||||||
|
|
||||||
|
if (seqlen > 0) {
|
||||||
|
cout << ':' << (tcp_hdr.seqno + seqlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << " ack " << tcp_hdr.ackno << " win " << tcp_hdr.win << " length " << payload_len << endl;
|
||||||
|
}
|
||||||
|
hexdump(payload.data(), payload.size(), 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
pcap_close(p_hdl);
|
||||||
|
if (next_ret == -1) {
|
||||||
|
cout << "Error listening for packet: " << pcap_geterr(p_hdl) << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
49
libsponge/tcp_connection.cc
Normal file
49
libsponge/tcp_connection.cc
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#include "tcp_connection.hh"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// Dummy implementation of a TCP connection
|
||||||
|
|
||||||
|
// For Lab 4, please replace with a real implementation that passes the
|
||||||
|
// automated checks run by `make check`.
|
||||||
|
|
||||||
|
template <typename... Targs>
|
||||||
|
void DUMMY_CODE(Targs &&... /* unused */) {}
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
size_t TCPConnection::remaining_outbound_capacity() const { return {}; }
|
||||||
|
|
||||||
|
size_t TCPConnection::bytes_in_flight() const { return {}; }
|
||||||
|
|
||||||
|
size_t TCPConnection::unassembled_bytes() const { return {}; }
|
||||||
|
|
||||||
|
size_t TCPConnection::time_since_last_segment_received() const { return {}; }
|
||||||
|
|
||||||
|
void TCPConnection::segment_received(const TCPSegment &seg) { DUMMY_CODE(seg); }
|
||||||
|
|
||||||
|
bool TCPConnection::active() const { return {}; }
|
||||||
|
|
||||||
|
size_t TCPConnection::write(const string &data) {
|
||||||
|
DUMMY_CODE(data);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
//! \param[in] ms_since_last_tick number of milliseconds since the last call to this method
|
||||||
|
void TCPConnection::tick(const size_t ms_since_last_tick) { DUMMY_CODE(ms_since_last_tick); }
|
||||||
|
|
||||||
|
void TCPConnection::end_input_stream() {}
|
||||||
|
|
||||||
|
void TCPConnection::connect() {}
|
||||||
|
|
||||||
|
TCPConnection::~TCPConnection() {
|
||||||
|
try {
|
||||||
|
if (active()) {
|
||||||
|
cerr << "Warning: Unclean shutdown of TCPConnection\n";
|
||||||
|
|
||||||
|
// Your code here: need to send a RST segment to the peer
|
||||||
|
}
|
||||||
|
} catch (const exception &e) {
|
||||||
|
std::cerr << "Exception destructing TCP FSM: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
99
libsponge/tcp_connection.hh
Normal file
99
libsponge/tcp_connection.hh
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
#ifndef SPONGE_LIBSPONGE_TCP_FACTORED_HH
|
||||||
|
#define SPONGE_LIBSPONGE_TCP_FACTORED_HH
|
||||||
|
|
||||||
|
#include "tcp_config.hh"
|
||||||
|
#include "tcp_receiver.hh"
|
||||||
|
#include "tcp_sender.hh"
|
||||||
|
#include "tcp_state.hh"
|
||||||
|
|
||||||
|
//! \brief A complete endpoint of a TCP connection
|
||||||
|
class TCPConnection {
|
||||||
|
private:
|
||||||
|
TCPConfig _cfg;
|
||||||
|
TCPReceiver _receiver{_cfg.recv_capacity};
|
||||||
|
TCPSender _sender{_cfg.send_capacity, _cfg.rt_timeout, _cfg.fixed_isn};
|
||||||
|
|
||||||
|
//! outbound queue of segments that the TCPConnection wants sent
|
||||||
|
std::queue<TCPSegment> _segments_out{};
|
||||||
|
|
||||||
|
//! Should the TCPConnection stay active (and keep ACKing)
|
||||||
|
//! for 10 * _cfg.rt_timeout milliseconds after both streams have ended,
|
||||||
|
//! in case the remote TCPConnection doesn't know we've received its whole stream?
|
||||||
|
bool _linger_after_streams_finish{true};
|
||||||
|
|
||||||
|
public:
|
||||||
|
//! \name "Input" interface for the writer
|
||||||
|
//!@{
|
||||||
|
|
||||||
|
//! \brief Initiate a connection by sending a SYN segment
|
||||||
|
void connect();
|
||||||
|
|
||||||
|
//! \brief Write data to the outbound byte stream, and send it over TCP if possible
|
||||||
|
//! \returns the number of bytes from `data` that were actually written.
|
||||||
|
size_t write(const std::string &data);
|
||||||
|
|
||||||
|
//! \returns the number of `bytes` that can be written right now.
|
||||||
|
size_t remaining_outbound_capacity() const;
|
||||||
|
|
||||||
|
//! \brief Shut down the outbound byte stream (still allows reading incoming data)
|
||||||
|
void end_input_stream();
|
||||||
|
//!@}
|
||||||
|
|
||||||
|
//! \name "Output" interface for the reader
|
||||||
|
//!@{
|
||||||
|
|
||||||
|
//! \brief The inbound byte stream received from the peer
|
||||||
|
ByteStream &inbound_stream() { return _receiver.stream_out(); }
|
||||||
|
//!@}
|
||||||
|
|
||||||
|
//! \name Accessors used for testing
|
||||||
|
|
||||||
|
//!@{
|
||||||
|
//! \brief number of bytes sent and not yet acknowledged, counting SYN/FIN each as one byte
|
||||||
|
size_t bytes_in_flight() const;
|
||||||
|
//! \brief number of bytes not yet reassembled
|
||||||
|
size_t unassembled_bytes() const;
|
||||||
|
//! \brief Number of milliseconds since the last segment was received
|
||||||
|
size_t time_since_last_segment_received() const;
|
||||||
|
//!< \brief summarize the state of the sender, receiver, and the connection
|
||||||
|
TCPState state() const { return {_sender, _receiver, active(), _linger_after_streams_finish}; };
|
||||||
|
//!@}
|
||||||
|
|
||||||
|
//! \name Methods for the owner or operating system to call
|
||||||
|
//!@{
|
||||||
|
|
||||||
|
//! Called when a new segment has been received from the network
|
||||||
|
void segment_received(const TCPSegment &seg);
|
||||||
|
|
||||||
|
//! Called periodically when time elapses
|
||||||
|
void tick(const size_t ms_since_last_tick);
|
||||||
|
|
||||||
|
//! \brief TCPSegments that the TCPConnection has enqueued for transmission.
|
||||||
|
//! \note The owner or operating system will dequeue these and
|
||||||
|
//! put each one into the payload of a lower-layer datagram (usually Internet datagrams (IP),
|
||||||
|
//! but could also be user datagrams (UDP) or any other kind).
|
||||||
|
std::queue<TCPSegment> &segments_out() { return _segments_out; }
|
||||||
|
|
||||||
|
//! \brief Is the connection still alive in any way?
|
||||||
|
//! \returns `true` if either stream is still running or if the TCPConnection is lingering
|
||||||
|
//! after both streams have finished (e.g. to ACK retransmissions from the peer)
|
||||||
|
bool active() const;
|
||||||
|
//!@}
|
||||||
|
|
||||||
|
//! Construct a new connection from a configuration
|
||||||
|
explicit TCPConnection(const TCPConfig &cfg) : _cfg{cfg} {}
|
||||||
|
|
||||||
|
//! \name construction and destruction
|
||||||
|
//! moving is allowed; copying is disallowed; default construction not possible
|
||||||
|
|
||||||
|
//!@{
|
||||||
|
~TCPConnection(); //!< destructor sends a RST if the connection is still open
|
||||||
|
TCPConnection() = delete;
|
||||||
|
TCPConnection(TCPConnection &&other) = default;
|
||||||
|
TCPConnection &operator=(TCPConnection &&other) = default;
|
||||||
|
TCPConnection(const TCPConnection &other) = delete;
|
||||||
|
TCPConnection &operator=(const TCPConnection &other) = delete;
|
||||||
|
//!@}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SPONGE_LIBSPONGE_TCP_FACTORED_HH
|
||||||
57
libsponge/tcp_helpers/fd_adapter.cc
Normal file
57
libsponge/tcp_helpers/fd_adapter.cc
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#include "fd_adapter.hh"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
//! \details This function first attempts to parse a TCP segment from the next UDP
|
||||||
|
//! payload recv()d from the socket.
|
||||||
|
//!
|
||||||
|
//! If this succeeds, it then checks that the received segment is related to the
|
||||||
|
//! current connection. When a TCP connection has been established, this means
|
||||||
|
//! checking that the source and destination ports in the TCP header are correct.
|
||||||
|
//!
|
||||||
|
//! If the TCP FSM is listening (i.e., TCPOverUDPSocketAdapter::_listen is `true`)
|
||||||
|
//! and the TCP segment read from the wire includes a SYN, this function clears the
|
||||||
|
//! `_listen` flag and calls calls connect() on the underlying UDP socket, with
|
||||||
|
//! the result that future outgoing segments go to the sender of the SYN segment.
|
||||||
|
//! \returns a std::optional<TCPSegment> that is empty if the segment was invalid or unrelated
|
||||||
|
optional<TCPSegment> TCPOverUDPSocketAdapter::read() {
|
||||||
|
auto datagram = _sock.recv();
|
||||||
|
|
||||||
|
// is it for us?
|
||||||
|
if (not listening() and (datagram.source_address != config().destination)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// is the payload a valid TCP segment?
|
||||||
|
TCPSegment seg;
|
||||||
|
if (ParseResult::NoError != seg.parse(move(datagram.payload), 0)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// should we target this source in all future replies?
|
||||||
|
if (listening()) {
|
||||||
|
if (seg.header().syn and not seg.header().rst) {
|
||||||
|
config_mutable().destination = datagram.source_address;
|
||||||
|
set_listening(false);
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return seg;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Serialize a TCP segment and send it as the payload of a UDP datagram.
|
||||||
|
//! \param[in] seg is the TCP segment to write
|
||||||
|
void TCPOverUDPSocketAdapter::write(TCPSegment &seg) {
|
||||||
|
seg.header().sport = config().source.port();
|
||||||
|
seg.header().dport = config().destination.port();
|
||||||
|
_sock.sendto(config().destination, seg.serialize(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Specialize LossyFdAdapter to TCPOverUDPSocketAdapter
|
||||||
|
template class LossyFdAdapter<TCPOverUDPSocketAdapter>;
|
||||||
70
libsponge/tcp_helpers/fd_adapter.hh
Normal file
70
libsponge/tcp_helpers/fd_adapter.hh
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#ifndef SPONGE_LIBSPONGE_FD_ADAPTER_HH
|
||||||
|
#define SPONGE_LIBSPONGE_FD_ADAPTER_HH
|
||||||
|
|
||||||
|
#include "file_descriptor.hh"
|
||||||
|
#include "lossy_fd_adapter.hh"
|
||||||
|
#include "socket.hh"
|
||||||
|
#include "tcp_config.hh"
|
||||||
|
#include "tcp_header.hh"
|
||||||
|
#include "tcp_segment.hh"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
//! \brief Basic functionality for file descriptor adaptors
|
||||||
|
//! \details See TCPOverUDPSocketAdapter and TCPOverIPv4OverTunFdAdapter for more information.
|
||||||
|
class FdAdapterBase {
|
||||||
|
private:
|
||||||
|
FdAdapterConfig _cfg{}; //!< Configuration values
|
||||||
|
bool _listen = false; //!< Is the connected TCP FSM in listen state?
|
||||||
|
|
||||||
|
protected:
|
||||||
|
FdAdapterConfig &config_mutable() { return _cfg; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
//! \brief Set the listening flag
|
||||||
|
//! \param[in] l is the new value for the flag
|
||||||
|
void set_listening(const bool l) { _listen = l; }
|
||||||
|
|
||||||
|
//! \brief Get the listening flag
|
||||||
|
//! \returns whether the FdAdapter is listening for a new connection
|
||||||
|
bool listening() const { return _listen; }
|
||||||
|
|
||||||
|
//! \brief Get the current configuration
|
||||||
|
//! \returns a const reference
|
||||||
|
const FdAdapterConfig &config() const { return _cfg; }
|
||||||
|
|
||||||
|
//! \brief Get the current configuration (mutable)
|
||||||
|
//! \returns a mutable reference
|
||||||
|
FdAdapterConfig &config_mut() { return _cfg; }
|
||||||
|
|
||||||
|
//! Called periodically when time elapses
|
||||||
|
void tick(const size_t) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
//! \brief A FD adaptor that reads and writes TCP segments in UDP payloads
|
||||||
|
class TCPOverUDPSocketAdapter : public FdAdapterBase {
|
||||||
|
private:
|
||||||
|
UDPSocket _sock;
|
||||||
|
|
||||||
|
public:
|
||||||
|
//! Construct from a UDPSocket sliced into a FileDescriptor
|
||||||
|
explicit TCPOverUDPSocketAdapter(UDPSocket &&sock) : _sock(std::move(sock)) {}
|
||||||
|
|
||||||
|
//! Attempts to read and return a TCP segment related to the current connection from a UDP payload
|
||||||
|
std::optional<TCPSegment> read();
|
||||||
|
|
||||||
|
//! Writes a TCP segment into a UDP payload
|
||||||
|
void write(TCPSegment &seg);
|
||||||
|
|
||||||
|
//! Access the underlying UDP socket
|
||||||
|
operator UDPSocket &() { return _sock; }
|
||||||
|
|
||||||
|
//! Access the underlying UDP socket
|
||||||
|
operator const UDPSocket &() const { return _sock; }
|
||||||
|
};
|
||||||
|
|
||||||
|
//! Typedef for TCPOverUDPSocketAdapter
|
||||||
|
using LossyTCPOverUDPSocketAdapter = LossyFdAdapter<TCPOverUDPSocketAdapter>;
|
||||||
|
|
||||||
|
#endif // SPONGE_LIBSPONGE_FD_ADAPTER_HH
|
||||||
41
libsponge/tcp_helpers/ipv4_datagram.cc
Normal file
41
libsponge/tcp_helpers/ipv4_datagram.cc
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#include "ipv4_datagram.hh"
|
||||||
|
|
||||||
|
#include "parser.hh"
|
||||||
|
#include "util.hh"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
ParseResult IPv4Datagram::parse(const Buffer buffer) {
|
||||||
|
NetParser p{buffer};
|
||||||
|
_header.parse(p);
|
||||||
|
_payload = p.buffer();
|
||||||
|
|
||||||
|
if (_payload.size() != _header.payload_length()) {
|
||||||
|
return ParseResult::PacketTooShort;
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.get_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferList IPv4Datagram::serialize() const {
|
||||||
|
if (_payload.size() != _header.payload_length()) {
|
||||||
|
throw runtime_error("IPv4Datagram::serialize: payload is wrong size");
|
||||||
|
}
|
||||||
|
|
||||||
|
IPv4Header header_out = _header;
|
||||||
|
header_out.cksum = 0;
|
||||||
|
const string header_zero_checksum = header_out.serialize();
|
||||||
|
|
||||||
|
// calculate checksum -- taken over header only
|
||||||
|
InternetChecksum check;
|
||||||
|
check.add(header_zero_checksum);
|
||||||
|
header_out.cksum = check.value();
|
||||||
|
|
||||||
|
BufferList ret;
|
||||||
|
ret.append(header_out.serialize());
|
||||||
|
ret.append(_payload);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
32
libsponge/tcp_helpers/ipv4_datagram.hh
Normal file
32
libsponge/tcp_helpers/ipv4_datagram.hh
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#ifndef SPONGE_LIBSPONGE_IPV4_DATAGRAM_HH
|
||||||
|
#define SPONGE_LIBSPONGE_IPV4_DATAGRAM_HH
|
||||||
|
|
||||||
|
#include "buffer.hh"
|
||||||
|
#include "ipv4_header.hh"
|
||||||
|
|
||||||
|
//! \brief [IPv4](\ref rfc::rfc791) Internet datagram
|
||||||
|
class IPv4Datagram {
|
||||||
|
private:
|
||||||
|
IPv4Header _header{};
|
||||||
|
BufferList _payload{};
|
||||||
|
|
||||||
|
public:
|
||||||
|
//! \brief Parse the segment from a string
|
||||||
|
ParseResult parse(const Buffer buffer);
|
||||||
|
|
||||||
|
//! \brief Serialize the segment to a string
|
||||||
|
BufferList serialize() const;
|
||||||
|
|
||||||
|
//! \name Accessors
|
||||||
|
//!@{
|
||||||
|
const IPv4Header &header() const { return _header; }
|
||||||
|
IPv4Header &header() { return _header; }
|
||||||
|
|
||||||
|
const BufferList &payload() const { return _payload; }
|
||||||
|
BufferList &payload() { return _payload; }
|
||||||
|
//!@}
|
||||||
|
};
|
||||||
|
|
||||||
|
using InternetDatagram = IPv4Datagram;
|
||||||
|
|
||||||
|
#endif // SPONGE_LIBSPONGE_IPV4_DATAGRAM_HH
|
||||||
159
libsponge/tcp_helpers/ipv4_header.cc
Normal file
159
libsponge/tcp_helpers/ipv4_header.cc
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
#include "ipv4_header.hh"
|
||||||
|
|
||||||
|
#include "util.hh"
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
//! \param[in,out] p is a NetParser from which the IP fields will be extracted
|
||||||
|
//! \returns a ParseResult indicating success or the reason for failure
|
||||||
|
//! \details It is important to check for (at least) the following potential errors
|
||||||
|
//! (but note that NetParser inherently checks for certain errors;
|
||||||
|
//! use that fact to your advantage!):
|
||||||
|
//!
|
||||||
|
//! - data stream is too short to contain a header
|
||||||
|
//! - wrong IP version number
|
||||||
|
//! - the header's `hlen` field is shorter than the minimum allowed
|
||||||
|
//! - there is less data in the header than the `doff` field claims
|
||||||
|
//! - there is less data in the full datagram than the `len` field claims
|
||||||
|
//! - the checksum is bad
|
||||||
|
ParseResult IPv4Header::parse(NetParser &p) {
|
||||||
|
Buffer original_serialized_version = p.buffer();
|
||||||
|
|
||||||
|
const size_t data_size = p.buffer().size();
|
||||||
|
if (data_size < IPv4Header::LENGTH) {
|
||||||
|
return ParseResult::PacketTooShort;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t first_byte = p.u8();
|
||||||
|
ver = first_byte >> 4; // version
|
||||||
|
hlen = first_byte & 0x0f; // header length
|
||||||
|
tos = p.u8(); // type of service
|
||||||
|
len = p.u16(); // length
|
||||||
|
id = p.u16(); // id
|
||||||
|
|
||||||
|
const uint16_t fo_val = p.u16();
|
||||||
|
df = static_cast<bool>(fo_val & 0x4000); // don't fragment
|
||||||
|
mf = static_cast<bool>(fo_val & 0x2000); // more fragments
|
||||||
|
offset = fo_val & 0x1fff; // offset
|
||||||
|
|
||||||
|
ttl = p.u8(); // ttl
|
||||||
|
proto = p.u8(); // proto
|
||||||
|
cksum = p.u16(); // checksum
|
||||||
|
src = p.u32(); // source address
|
||||||
|
dst = p.u32(); // destination address
|
||||||
|
|
||||||
|
if (data_size < 4 * hlen) {
|
||||||
|
return ParseResult::PacketTooShort;
|
||||||
|
}
|
||||||
|
if (ver != 4) {
|
||||||
|
return ParseResult::WrongIPVersion;
|
||||||
|
}
|
||||||
|
if (hlen < 5) {
|
||||||
|
return ParseResult::HeaderTooShort;
|
||||||
|
}
|
||||||
|
if (data_size != len) {
|
||||||
|
return ParseResult::TruncatedPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.remove_prefix(hlen * 4 - IPv4Header::LENGTH);
|
||||||
|
|
||||||
|
if (p.error()) {
|
||||||
|
return p.get_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
InternetChecksum check;
|
||||||
|
check.add({original_serialized_version.str().data(), size_t(4 * hlen)});
|
||||||
|
if (check.value()) {
|
||||||
|
return ParseResult::BadChecksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseResult::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Serialize the IPv4Header to a string (does not recompute the checksum)
|
||||||
|
string IPv4Header::serialize() const {
|
||||||
|
// sanity checks
|
||||||
|
if (ver != 4) {
|
||||||
|
throw runtime_error("wrong IP version");
|
||||||
|
}
|
||||||
|
if (4 * hlen < IPv4Header::LENGTH) {
|
||||||
|
throw runtime_error("IP header too short");
|
||||||
|
}
|
||||||
|
|
||||||
|
string ret;
|
||||||
|
ret.reserve(4 * hlen);
|
||||||
|
|
||||||
|
const uint8_t first_byte = (ver << 4) | (hlen & 0xf);
|
||||||
|
NetUnparser::u8(ret, first_byte); // version and header length
|
||||||
|
NetUnparser::u8(ret, tos); // type of service
|
||||||
|
NetUnparser::u16(ret, len); // length
|
||||||
|
NetUnparser::u16(ret, id); // id
|
||||||
|
|
||||||
|
const uint16_t fo_val = (df ? 0x4000 : 0) | (mf ? 0x2000 : 0) | (offset & 0x1fff);
|
||||||
|
NetUnparser::u16(ret, fo_val); // flags and offset
|
||||||
|
|
||||||
|
NetUnparser::u8(ret, ttl); // time to live
|
||||||
|
NetUnparser::u8(ret, proto); // protocol number
|
||||||
|
|
||||||
|
NetUnparser::u16(ret, cksum); // checksum
|
||||||
|
|
||||||
|
NetUnparser::u32(ret, src); // src address
|
||||||
|
NetUnparser::u32(ret, dst); // dst address
|
||||||
|
|
||||||
|
ret.resize(4 * hlen); // expand header to advertised size
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t IPv4Header::payload_length() const { return len - 4 * hlen; }
|
||||||
|
|
||||||
|
//! \details This value is needed when computing the checksum of an encapsulated TCP segment.
|
||||||
|
//! ~~~{.txt}
|
||||||
|
//! 0 7 8 15 16 23 24 31
|
||||||
|
//! +--------+--------+--------+--------+
|
||||||
|
//! | source address |
|
||||||
|
//! +--------+--------+--------+--------+
|
||||||
|
//! | destination address |
|
||||||
|
//! +--------+--------+--------+--------+
|
||||||
|
//! | zero |protocol| payload length |
|
||||||
|
//! +--------+--------+--------+--------+
|
||||||
|
//! ~~~
|
||||||
|
uint32_t IPv4Header::pseudo_cksum() const {
|
||||||
|
uint32_t pcksum = (src >> 16) + (src & 0xffff); // source addr
|
||||||
|
pcksum += (dst >> 16) + (dst & 0xffff); // dest addr
|
||||||
|
pcksum += proto; // protocol
|
||||||
|
pcksum += payload_length(); // payload length
|
||||||
|
return pcksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! \returns A string with the header's contents
|
||||||
|
std::string IPv4Header::to_string() const {
|
||||||
|
stringstream ss{};
|
||||||
|
ss << hex << boolalpha << "IP version: " << +ver << '\n'
|
||||||
|
<< "IP hdr len: " << +hlen << '\n'
|
||||||
|
<< "IP tos: " << +tos << '\n'
|
||||||
|
<< "IP dgram len: " << +len << '\n'
|
||||||
|
<< "IP id: " << +id << '\n'
|
||||||
|
<< "Flags: df: " << df << " mf: " << mf << '\n'
|
||||||
|
<< "Offset: " << +offset << '\n'
|
||||||
|
<< "TTL: " << +ttl << '\n'
|
||||||
|
<< "Protocol: " << +proto << '\n'
|
||||||
|
<< "Checksum: " << +cksum << '\n'
|
||||||
|
<< "Src addr: " << +src << '\n'
|
||||||
|
<< "Dst addr: " << +dst << '\n';
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string IPv4Header::summary() const {
|
||||||
|
stringstream ss{};
|
||||||
|
ss << hex << boolalpha << "IPv" << +ver << ", "
|
||||||
|
<< "len=" << +len << ", "
|
||||||
|
<< "protocol=" << +proto << ", " << (ttl >= 10 ? "" : "ttl=" + ::to_string(ttl) + ", ")
|
||||||
|
<< "src=" << inet_ntoa({htobe32(src)}) << ", "
|
||||||
|
<< "dst=" << inet_ntoa({htobe32(dst)});
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
71
libsponge/tcp_helpers/ipv4_header.hh
Normal file
71
libsponge/tcp_helpers/ipv4_header.hh
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#ifndef SPONGE_LIBSPONGE_IPV4_HEADER_HH
|
||||||
|
#define SPONGE_LIBSPONGE_IPV4_HEADER_HH
|
||||||
|
|
||||||
|
#include "parser.hh"
|
||||||
|
|
||||||
|
//! \brief [IPv4](\ref rfc::rfc791) Internet datagram header
|
||||||
|
//! \note IP options are not supported
|
||||||
|
struct IPv4Header {
|
||||||
|
static constexpr size_t LENGTH = 20; //!< [IPv4](\ref rfc::rfc791) header length, not including options
|
||||||
|
static constexpr uint8_t DEFAULT_TTL = 128; //!< A reasonable default TTL value
|
||||||
|
static constexpr uint8_t PROTO_TCP = 6; //!< Protocol number for [tcp](\ref rfc::rfc793)
|
||||||
|
|
||||||
|
//! \struct IPv4Header
|
||||||
|
//! ~~~{.txt}
|
||||||
|
//! 0 1 2 3
|
||||||
|
//! 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
//! |Version| IHL |Type of Service| Total Length |
|
||||||
|
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
//! | Identification |Flags| Fragment Offset |
|
||||||
|
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
//! | Time to Live | Protocol | Header Checksum |
|
||||||
|
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
//! | Source Address |
|
||||||
|
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
//! | Destination Address |
|
||||||
|
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
//! | Options | Padding |
|
||||||
|
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
//! ~~~
|
||||||
|
|
||||||
|
//! \name IPv4 Header fields
|
||||||
|
//!@{
|
||||||
|
uint8_t ver = 4; //!< IP version
|
||||||
|
uint8_t hlen = LENGTH / 4; //!< header length (multiples of 32 bits)
|
||||||
|
uint8_t tos = 0; //!< type of service
|
||||||
|
uint16_t len = 0; //!< total length of packet
|
||||||
|
uint16_t id = 0; //!< identification number
|
||||||
|
bool df = true; //!< don't fragment flag
|
||||||
|
bool mf = false; //!< more fragments flag
|
||||||
|
uint16_t offset = 0; //!< fragment offset field
|
||||||
|
uint8_t ttl = DEFAULT_TTL; //!< time to live field
|
||||||
|
uint8_t proto = PROTO_TCP; //!< protocol field
|
||||||
|
uint16_t cksum = 0; //!< checksum field
|
||||||
|
uint32_t src = 0; //!< src address
|
||||||
|
uint32_t dst = 0; //!< dst address
|
||||||
|
//!@}
|
||||||
|
|
||||||
|
//! Parse the IP fields from the provided NetParser
|
||||||
|
ParseResult parse(NetParser &p);
|
||||||
|
|
||||||
|
//! Serialize the IP fields
|
||||||
|
std::string serialize() const;
|
||||||
|
|
||||||
|
//! Length of the payload
|
||||||
|
uint16_t payload_length() const;
|
||||||
|
|
||||||
|
//! [pseudo-header's](\ref rfc::rfc793) contribution to the TCP checksum
|
||||||
|
uint32_t pseudo_cksum() const;
|
||||||
|
|
||||||
|
//! Return a string containing a header in human-readable format
|
||||||
|
std::string to_string() const;
|
||||||
|
|
||||||
|
//! Return a string containing a human-readable summary of the header
|
||||||
|
std::string summary() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
//! \struct IPv4Header
|
||||||
|
//! This struct can be used to parse an existing IP header or to create a new one.
|
||||||
|
|
||||||
|
#endif // SPONGE_LIBSPONGE_IPV4_HEADER_HH
|
||||||
72
libsponge/tcp_helpers/lossy_fd_adapter.hh
Normal file
72
libsponge/tcp_helpers/lossy_fd_adapter.hh
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#ifndef SPONGE_LIBSPONGE_LOSSY_FD_ADAPTER_HH
|
||||||
|
#define SPONGE_LIBSPONGE_LOSSY_FD_ADAPTER_HH
|
||||||
|
|
||||||
|
#include "file_descriptor.hh"
|
||||||
|
#include "tcp_config.hh"
|
||||||
|
#include "tcp_segment.hh"
|
||||||
|
#include "util.hh"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <random>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
//! An adapter class that adds random dropping behavior to an FD adapter
|
||||||
|
template <typename AdapterT>
|
||||||
|
class LossyFdAdapter {
|
||||||
|
private:
|
||||||
|
//! Fast RNG used by _should_drop()
|
||||||
|
std::mt19937 _rand{get_random_generator()};
|
||||||
|
|
||||||
|
//! The underlying FD adapter
|
||||||
|
AdapterT _adapter;
|
||||||
|
|
||||||
|
//! \brief Determine whether or not to drop a given read or write
|
||||||
|
//! \param[in] uplink is `true` to use the uplink loss probability, else use the downlink loss probability
|
||||||
|
//! \returns `true` if the segment should be dropped
|
||||||
|
bool _should_drop(bool uplink) {
|
||||||
|
const auto &cfg = _adapter.config();
|
||||||
|
const uint16_t loss = uplink ? cfg.loss_rate_up : cfg.loss_rate_dn;
|
||||||
|
return loss != 0 && uint16_t(_rand()) < loss;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
//! Conversion to a FileDescriptor by returning the underlying AdapterT
|
||||||
|
operator const FileDescriptor &() const { return _adapter; }
|
||||||
|
|
||||||
|
//! Construct from a FileDescriptor appropriate to the AdapterT constructor
|
||||||
|
explicit LossyFdAdapter(AdapterT &&adapter) : _adapter(std::move(adapter)) {}
|
||||||
|
|
||||||
|
//! \brief Read from the underlying AdapterT instance, potentially dropping the read datagram
|
||||||
|
//! \returns std::optional<TCPSegment> that is empty if the segment was dropped or if
|
||||||
|
//! the underlying AdapterT returned an empty value
|
||||||
|
std::optional<TCPSegment> read() {
|
||||||
|
auto ret = _adapter.read();
|
||||||
|
if (_should_drop(false)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! \brief Write to the underlying AdapterT instance, potentially dropping the datagram to be written
|
||||||
|
//! \param[in] seg is the packet to either write or drop
|
||||||
|
void write(TCPSegment &seg) {
|
||||||
|
if (_should_drop(true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return _adapter.write(seg);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! \name
|
||||||
|
//! Passthrough functions to the underlying AdapterT instance
|
||||||
|
|
||||||
|
//!@{
|
||||||
|
void set_listening(const bool l) { _adapter.set_listening(l); } //!< FdAdapterBase::set_listening passthrough
|
||||||
|
const FdAdapterConfig &config() const { return _adapter.config(); } //!< FdAdapterBase::config passthrough
|
||||||
|
FdAdapterConfig &config_mut() { return _adapter.config_mut(); } //!< FdAdapterBase::config_mut passthrough
|
||||||
|
void tick(const size_t ms_since_last_tick) {
|
||||||
|
_adapter.tick(ms_since_last_tick);
|
||||||
|
} //!< FdAdapterBase::tick passthrough
|
||||||
|
//!@}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SPONGE_LIBSPONGE_LOSSY_FD_ADAPTER_HH
|
||||||
@ -12,7 +12,7 @@
|
|||||||
class TCPConfig {
|
class TCPConfig {
|
||||||
public:
|
public:
|
||||||
static constexpr size_t DEFAULT_CAPACITY = 64000; //!< Default capacity
|
static constexpr size_t DEFAULT_CAPACITY = 64000; //!< Default capacity
|
||||||
static constexpr size_t MAX_PAYLOAD_SIZE = 1452; //!< Max TCP payload that fits in either IPv4 or UDP datagram
|
static constexpr size_t MAX_PAYLOAD_SIZE = 1000; //!< Conservative max payload size for real Internet
|
||||||
static constexpr uint16_t TIMEOUT_DFLT = 1000; //!< Default re-transmit timeout is 1 second
|
static constexpr uint16_t TIMEOUT_DFLT = 1000; //!< Default re-transmit timeout is 1 second
|
||||||
static constexpr unsigned MAX_RETX_ATTEMPTS = 8; //!< Maximum re-transmit attempts before giving up
|
static constexpr unsigned MAX_RETX_ATTEMPTS = 8; //!< Maximum re-transmit attempts before giving up
|
||||||
|
|
||||||
|
|||||||
90
libsponge/tcp_helpers/tcp_over_ip.cc
Normal file
90
libsponge/tcp_helpers/tcp_over_ip.cc
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#include "tcp_over_ip.hh"
|
||||||
|
|
||||||
|
#include "ipv4_datagram.hh"
|
||||||
|
#include "ipv4_header.hh"
|
||||||
|
#include "parser.hh"
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
//! \details This function attempts to parse a TCP segment from
|
||||||
|
//! the IP datagram's payload.
|
||||||
|
//!
|
||||||
|
//! If this succeeds, it then checks that the received segment is related to the
|
||||||
|
//! current connection. When a TCP connection has been established, this means
|
||||||
|
//! checking that the source and destination ports in the TCP header are correct.
|
||||||
|
//!
|
||||||
|
//! If the TCP connection is listening (i.e., TCPOverIPv4OverTunFdAdapter::_listen is `true`)
|
||||||
|
//! and the TCP segment read from the wire includes a SYN, this function clears the
|
||||||
|
//! `_listen` flag and records the source and destination addresses and port numbers
|
||||||
|
//! from the TCP header; it uses this information to filter future reads.
|
||||||
|
//! \returns a std::optional<TCPSegment> that is empty if the segment was invalid or unrelated
|
||||||
|
optional<TCPSegment> TCPOverIPv4Adapter::unwrap_tcp_in_ip(const InternetDatagram &ip_dgram) {
|
||||||
|
// is the IPv4 datagram for us?
|
||||||
|
// Note: it's valid to bind to address "0" (INADDR_ANY) and reply from actual address contacted
|
||||||
|
if (not listening() and (ip_dgram.header().dst != config().source.ipv4_numeric())) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// is the IPv4 datagram from our peer?
|
||||||
|
if (not listening() and (ip_dgram.header().src != config().destination.ipv4_numeric())) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// does the IPv4 datagram claim that its payload is a TCP segment?
|
||||||
|
if (ip_dgram.header().proto != IPv4Header::PROTO_TCP) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// is the payload a valid TCP segment?
|
||||||
|
TCPSegment tcp_seg;
|
||||||
|
if (ParseResult::NoError != tcp_seg.parse(ip_dgram.payload(), ip_dgram.header().pseudo_cksum())) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// is the TCP segment for us?
|
||||||
|
if (tcp_seg.header().dport != config().source.port()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// should we target this source addr/port (and use its destination addr as our source) in reply?
|
||||||
|
if (listening()) {
|
||||||
|
if (tcp_seg.header().syn and not tcp_seg.header().rst) {
|
||||||
|
config_mutable().source = {inet_ntoa({htobe32(ip_dgram.header().dst)}), config().source.port()};
|
||||||
|
config_mutable().destination = {inet_ntoa({htobe32(ip_dgram.header().src)}), tcp_seg.header().sport};
|
||||||
|
set_listening(false);
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// is the TCP segment from our peer?
|
||||||
|
if (tcp_seg.header().sport != config().destination.port()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return tcp_seg;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Takes a TCP segment, sets port numbers as necessary, and wraps it in an IPv4 datagram
|
||||||
|
//! \param[in] seg is the TCP segment to convert
|
||||||
|
InternetDatagram TCPOverIPv4Adapter::wrap_tcp_in_ip(TCPSegment &seg) {
|
||||||
|
// set the port numbers in the TCP segment
|
||||||
|
seg.header().sport = config().source.port();
|
||||||
|
seg.header().dport = config().destination.port();
|
||||||
|
|
||||||
|
// create an Internet Datagram and set its addresses and length
|
||||||
|
InternetDatagram ip_dgram;
|
||||||
|
ip_dgram.header().src = config().source.ipv4_numeric();
|
||||||
|
ip_dgram.header().dst = config().destination.ipv4_numeric();
|
||||||
|
ip_dgram.header().len = ip_dgram.header().hlen * 4 + seg.header().doff * 4 + seg.payload().size();
|
||||||
|
|
||||||
|
// set payload, calculating TCP checksum using information from IP header
|
||||||
|
ip_dgram.payload() = seg.serialize(ip_dgram.header().pseudo_cksum());
|
||||||
|
|
||||||
|
return ip_dgram;
|
||||||
|
}
|
||||||
19
libsponge/tcp_helpers/tcp_over_ip.hh
Normal file
19
libsponge/tcp_helpers/tcp_over_ip.hh
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef SPONGE_LIBSPONGE_TCP_OVER_IP_HH
|
||||||
|
#define SPONGE_LIBSPONGE_TCP_OVER_IP_HH
|
||||||
|
|
||||||
|
#include "buffer.hh"
|
||||||
|
#include "fd_adapter.hh"
|
||||||
|
#include "ipv4_datagram.hh"
|
||||||
|
#include "tcp_segment.hh"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
//! \brief A converter from TCP segments to serialized IPv4 datagrams
|
||||||
|
class TCPOverIPv4Adapter : public FdAdapterBase {
|
||||||
|
public:
|
||||||
|
std::optional<TCPSegment> unwrap_tcp_in_ip(const InternetDatagram &ip_dgram);
|
||||||
|
|
||||||
|
InternetDatagram wrap_tcp_in_ip(TCPSegment &seg);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SPONGE_LIBSPONGE_TCP_OVER_IP_HH
|
||||||
295
libsponge/tcp_helpers/tcp_sponge_socket.cc
Normal file
295
libsponge/tcp_helpers/tcp_sponge_socket.cc
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
#include "tcp_sponge_socket.hh"
|
||||||
|
|
||||||
|
#include "parser.hh"
|
||||||
|
#include "tun.hh"
|
||||||
|
#include "util.hh"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <exception>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
static constexpr size_t TCP_TICK_MS = 10;
|
||||||
|
|
||||||
|
//! \param[in] condition is a function returning true if loop should continue
|
||||||
|
template <typename AdaptT>
|
||||||
|
void TCPSpongeSocket<AdaptT>::_tcp_loop(const function<bool()> &condition) {
|
||||||
|
auto base_time = timestamp_ms();
|
||||||
|
while (condition()) {
|
||||||
|
auto ret = _eventloop.wait_next_event(TCP_TICK_MS);
|
||||||
|
if (ret == EventLoop::Result::Exit or _abort) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_tcp.value().active()) {
|
||||||
|
const auto next_time = timestamp_ms();
|
||||||
|
_tcp.value().tick(next_time - base_time);
|
||||||
|
_datagram_adapter.tick(next_time - base_time);
|
||||||
|
base_time = next_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//! \param[in] data_socket_pair is a pair of connected AF_UNIX SOCK_STREAM sockets
|
||||||
|
//! \param[in] datagram_interface is the interface for reading and writing datagrams
|
||||||
|
template <typename AdaptT>
|
||||||
|
TCPSpongeSocket<AdaptT>::TCPSpongeSocket(pair<FileDescriptor, FileDescriptor> data_socket_pair,
|
||||||
|
AdaptT &&datagram_interface)
|
||||||
|
: LocalStreamSocket(move(data_socket_pair.first))
|
||||||
|
, _thread_data(move(data_socket_pair.second))
|
||||||
|
, _datagram_adapter(move(datagram_interface)) {
|
||||||
|
_thread_data.set_blocking(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename AdaptT>
|
||||||
|
void TCPSpongeSocket<AdaptT>::_initialize_TCP(const TCPConfig &config) {
|
||||||
|
_tcp.emplace(config);
|
||||||
|
|
||||||
|
// Set up the event loop
|
||||||
|
|
||||||
|
// There are four possible events to handle:
|
||||||
|
//
|
||||||
|
// 1) Incoming datagram received (needs to be given to
|
||||||
|
// TCPConnection::segment_received method)
|
||||||
|
//
|
||||||
|
// 2) Outbound bytes received from local application via a write()
|
||||||
|
// call (needs to be read from the local stream socket and
|
||||||
|
// given to TCPConnection::data_written method)
|
||||||
|
//
|
||||||
|
// 3) Incoming bytes reassembled by the TCPConnection
|
||||||
|
// (needs to be read from the inbound_stream and written
|
||||||
|
// to the local stream socket back to the application)
|
||||||
|
//
|
||||||
|
// 4) Outbound segment generated by TCP (needs to be
|
||||||
|
// given to underlying datagram socket)
|
||||||
|
|
||||||
|
// rule 1: read from filtered packet stream and dump into TCPConnection
|
||||||
|
_eventloop.add_rule(_datagram_adapter,
|
||||||
|
Direction::In,
|
||||||
|
[&] {
|
||||||
|
auto seg = _datagram_adapter.read();
|
||||||
|
if (seg) {
|
||||||
|
_tcp->segment_received(move(seg.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// debugging output:
|
||||||
|
if (_thread_data.eof() and _tcp.value().bytes_in_flight() == 0 and not _fully_acked) {
|
||||||
|
cerr << "DEBUG: Outbound stream to "
|
||||||
|
<< _datagram_adapter.config().destination.to_string()
|
||||||
|
<< " has been fully acknowledged.\n";
|
||||||
|
_fully_acked = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[&] { return _tcp->active(); });
|
||||||
|
|
||||||
|
// rule 2: read from pipe into outbound buffer
|
||||||
|
_eventloop.add_rule(
|
||||||
|
_thread_data,
|
||||||
|
Direction::In,
|
||||||
|
[&] {
|
||||||
|
const auto data = _thread_data.read(_tcp->remaining_outbound_capacity());
|
||||||
|
const auto len = data.size();
|
||||||
|
const auto amount_written = _tcp->write(move(data));
|
||||||
|
if (amount_written != len) {
|
||||||
|
throw runtime_error("TCPConnection::write() accepted less than advertised length");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_thread_data.eof()) {
|
||||||
|
_tcp->end_input_stream();
|
||||||
|
_outbound_shutdown = true;
|
||||||
|
|
||||||
|
// debugging output:
|
||||||
|
cerr << "DEBUG: Outbound stream to " << _datagram_adapter.config().destination.to_string()
|
||||||
|
<< " finished (" << _tcp.value().bytes_in_flight() << " byte"
|
||||||
|
<< (_tcp.value().bytes_in_flight() == 1 ? "" : "s") << " still in flight).\n";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[&] { return (_tcp->active()) and (not _outbound_shutdown) and (_tcp->remaining_outbound_capacity() > 0); },
|
||||||
|
[&] {
|
||||||
|
_tcp->end_input_stream();
|
||||||
|
_outbound_shutdown = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// rule 3: read from inbound buffer into pipe
|
||||||
|
_eventloop.add_rule(
|
||||||
|
_thread_data,
|
||||||
|
Direction::Out,
|
||||||
|
[&] {
|
||||||
|
ByteStream &inbound = _tcp->inbound_stream();
|
||||||
|
// Write from the inbound_stream into
|
||||||
|
// the pipe, handling the possibility of a partial
|
||||||
|
// write (i.e., only pop what was actually written).
|
||||||
|
const size_t amount_to_write = min(size_t(65536), inbound.buffer_size());
|
||||||
|
const std::string buffer = inbound.peek_output(amount_to_write);
|
||||||
|
const auto bytes_written = _thread_data.write(move(buffer), false);
|
||||||
|
inbound.pop_output(bytes_written);
|
||||||
|
|
||||||
|
if (inbound.eof() or inbound.error()) {
|
||||||
|
_thread_data.shutdown(SHUT_WR);
|
||||||
|
_inbound_shutdown = true;
|
||||||
|
|
||||||
|
// debugging output:
|
||||||
|
cerr << "DEBUG: Inbound stream from " << _datagram_adapter.config().destination.to_string()
|
||||||
|
<< " finished " << (inbound.error() ? "with an error/reset.\n" : "cleanly.\n");
|
||||||
|
if (_tcp.value().state() == TCPState::State::TIME_WAIT) {
|
||||||
|
cerr << "DEBUG: Waiting for lingering segments (e.g. retransmissions of FIN) from peer...\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
return (not _tcp->inbound_stream().buffer_empty()) or
|
||||||
|
((_tcp->inbound_stream().eof() or _tcp->inbound_stream().error()) and not _inbound_shutdown);
|
||||||
|
});
|
||||||
|
|
||||||
|
// rule 4: read outbound segments from TCPConnection and send as datagrams
|
||||||
|
_eventloop.add_rule(_datagram_adapter,
|
||||||
|
Direction::Out,
|
||||||
|
[&] {
|
||||||
|
while (not _tcp->segments_out().empty()) {
|
||||||
|
_datagram_adapter.write(_tcp->segments_out().front());
|
||||||
|
_tcp->segments_out().pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[&] { return not _tcp->segments_out().empty(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
//! \brief Call [socketpair](\ref man2::socketpair) and return connected Unix-domain sockets of specified type
|
||||||
|
//! \param[in] type is the type of AF_UNIX sockets to create (e.g., SOCK_SEQPACKET)
|
||||||
|
//! \returns a std::pair of connected sockets
|
||||||
|
static inline pair<FileDescriptor, FileDescriptor> socket_pair_helper(const int type) {
|
||||||
|
int fds[2];
|
||||||
|
SystemCall("socketpair", ::socketpair(AF_UNIX, type, 0, static_cast<int *>(fds)));
|
||||||
|
return {FileDescriptor(fds[0]), FileDescriptor(fds[1])};
|
||||||
|
}
|
||||||
|
|
||||||
|
//! \param[in] datagram_interface is the underlying interface (e.g. to UDP, IP, or Ethernet)
|
||||||
|
template <typename AdaptT>
|
||||||
|
TCPSpongeSocket<AdaptT>::TCPSpongeSocket(AdaptT &&datagram_interface)
|
||||||
|
: TCPSpongeSocket(socket_pair_helper(SOCK_STREAM), move(datagram_interface)) {}
|
||||||
|
|
||||||
|
template <typename AdaptT>
|
||||||
|
TCPSpongeSocket<AdaptT>::~TCPSpongeSocket() {
|
||||||
|
try {
|
||||||
|
if (_tcp_thread.joinable()) {
|
||||||
|
cerr << "Warning: unclean shutdown of TCPSpongeSocket\n";
|
||||||
|
// force the other side to exit
|
||||||
|
_abort.store(true);
|
||||||
|
_tcp_thread.join();
|
||||||
|
}
|
||||||
|
} catch (const exception &e) {
|
||||||
|
cerr << "Exception destructing TCPSpongeSocket: " << e.what() << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename AdaptT>
|
||||||
|
void TCPSpongeSocket<AdaptT>::wait_until_closed() {
|
||||||
|
shutdown(SHUT_RDWR);
|
||||||
|
if (_tcp_thread.joinable()) {
|
||||||
|
cerr << "DEBUG: Waiting for clean shutdown... ";
|
||||||
|
_tcp_thread.join();
|
||||||
|
cerr << "done.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//! \param[in] c_tcp is the TCPConfig for the TCPConnection
|
||||||
|
//! \param[in] c_ad is the FdAdapterConfig for the FdAdapter
|
||||||
|
template <typename AdaptT>
|
||||||
|
void TCPSpongeSocket<AdaptT>::connect(const TCPConfig &c_tcp, const FdAdapterConfig &c_ad) {
|
||||||
|
if (_tcp) {
|
||||||
|
throw runtime_error("connect() with TCPConnection already initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
_initialize_TCP(c_tcp);
|
||||||
|
|
||||||
|
_datagram_adapter.config_mut() = c_ad;
|
||||||
|
|
||||||
|
cerr << "DEBUG: Connecting to " << c_ad.destination.to_string() << "... ";
|
||||||
|
_tcp->connect();
|
||||||
|
|
||||||
|
const TCPState expected_state = TCPState::State::SYN_SENT;
|
||||||
|
|
||||||
|
if (_tcp->state() != expected_state) {
|
||||||
|
throw runtime_error("After TCPConnection::connect(), state was " + _tcp->state().name() + " but expected " +
|
||||||
|
expected_state.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
_tcp_loop([&] { return _tcp->state() == TCPState::State::SYN_SENT; });
|
||||||
|
cerr << "done.\n";
|
||||||
|
|
||||||
|
_tcp_thread = thread(&TCPSpongeSocket::_tcp_main, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! \param[in] c_tcp is the TCPConfig for the TCPConnection
|
||||||
|
//! \param[in] c_ad is the FdAdapterConfig for the FdAdapter
|
||||||
|
template <typename AdaptT>
|
||||||
|
void TCPSpongeSocket<AdaptT>::listen_and_accept(const TCPConfig &c_tcp, const FdAdapterConfig &c_ad) {
|
||||||
|
if (_tcp) {
|
||||||
|
throw runtime_error("listen_and_accept() with TCPConnection already initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
_initialize_TCP(c_tcp);
|
||||||
|
|
||||||
|
_datagram_adapter.config_mut() = c_ad;
|
||||||
|
_datagram_adapter.set_listening(true);
|
||||||
|
|
||||||
|
cerr << "DEBUG: Listening for incoming connection... ";
|
||||||
|
_tcp_loop([&] {
|
||||||
|
const auto s = _tcp->state();
|
||||||
|
return (s == TCPState::State::LISTEN or s == TCPState::State::SYN_RCVD or s == TCPState::State::SYN_SENT);
|
||||||
|
});
|
||||||
|
cerr << "new connection from " << _datagram_adapter.config().destination.to_string() << ".\n";
|
||||||
|
|
||||||
|
_tcp_thread = thread(&TCPSpongeSocket::_tcp_main, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename AdaptT>
|
||||||
|
void TCPSpongeSocket<AdaptT>::_tcp_main() {
|
||||||
|
try {
|
||||||
|
if (not _tcp.has_value()) {
|
||||||
|
throw runtime_error("no TCP");
|
||||||
|
}
|
||||||
|
_tcp_loop([] { return true; });
|
||||||
|
shutdown(SHUT_RDWR);
|
||||||
|
if (not _tcp.value().active()) {
|
||||||
|
cerr << "DEBUG: TCP connection finished "
|
||||||
|
<< (_tcp.value().state() == TCPState::State::RESET ? "uncleanly" : "cleanly.\n");
|
||||||
|
}
|
||||||
|
_tcp.reset();
|
||||||
|
} catch (const exception &e) {
|
||||||
|
cerr << "Exception in TCPConnection runner thread: " << e.what() << "\n";
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Specialization of TCPSpongeSocket for TCPOverUDPSocketAdapter
|
||||||
|
template class TCPSpongeSocket<TCPOverUDPSocketAdapter>;
|
||||||
|
|
||||||
|
//! Specialization of TCPSpongeSocket for TCPOverIPv4OverTunFdAdapter
|
||||||
|
template class TCPSpongeSocket<TCPOverIPv4OverTunFdAdapter>;
|
||||||
|
|
||||||
|
//! Specialization of TCPSpongeSocket for LossyTCPOverUDPSocketAdapter
|
||||||
|
template class TCPSpongeSocket<LossyTCPOverUDPSocketAdapter>;
|
||||||
|
|
||||||
|
//! Specialization of TCPSpongeSocket for LossyTCPOverIPv4OverTunFdAdapter
|
||||||
|
template class TCPSpongeSocket<LossyTCPOverIPv4OverTunFdAdapter>;
|
||||||
|
|
||||||
|
CS144TCPSocket::CS144TCPSocket() : TCPOverIPv4SpongeSocket(TCPOverIPv4OverTunFdAdapter(TunFD("tun144"))) {}
|
||||||
|
|
||||||
|
void CS144TCPSocket::connect(const Address &address) {
|
||||||
|
TCPConfig tcp_config;
|
||||||
|
tcp_config.rt_timeout = 100;
|
||||||
|
|
||||||
|
FdAdapterConfig multiplexer_config;
|
||||||
|
multiplexer_config.source = {"169.254.144.9", to_string(uint16_t(random_device()()))};
|
||||||
|
multiplexer_config.destination = address;
|
||||||
|
|
||||||
|
TCPOverIPv4SpongeSocket::connect(tcp_config, multiplexer_config);
|
||||||
|
}
|
||||||
129
libsponge/tcp_helpers/tcp_sponge_socket.hh
Normal file
129
libsponge/tcp_helpers/tcp_sponge_socket.hh
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
#ifndef SPONGE_LIBSPONGE_TCP_SPONGE_SOCKET_HH
|
||||||
|
#define SPONGE_LIBSPONGE_TCP_SPONGE_SOCKET_HH
|
||||||
|
|
||||||
|
#include "byte_stream.hh"
|
||||||
|
#include "eventloop.hh"
|
||||||
|
#include "fd_adapter.hh"
|
||||||
|
#include "file_descriptor.hh"
|
||||||
|
#include "tcp_config.hh"
|
||||||
|
#include "tcp_connection.hh"
|
||||||
|
#include "tcp_over_ip.hh"
|
||||||
|
#include "tuntap_adapter.hh"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
//! Multithreaded wrapper around TCPConnection that approximates the Unix sockets API
|
||||||
|
template <typename AdaptT>
|
||||||
|
class TCPSpongeSocket : public LocalStreamSocket {
|
||||||
|
private:
|
||||||
|
//! Stream socket for reads and writes between owner and TCP thread
|
||||||
|
LocalStreamSocket _thread_data;
|
||||||
|
|
||||||
|
//! Adapter to underlying datagram socket (e.g., UDP or IP)
|
||||||
|
AdaptT _datagram_adapter;
|
||||||
|
|
||||||
|
//! Set up the TCPConnection and the event loop
|
||||||
|
void _initialize_TCP(const TCPConfig &config);
|
||||||
|
|
||||||
|
//! TCP state machine
|
||||||
|
std::optional<TCPConnection> _tcp{};
|
||||||
|
|
||||||
|
//! eventloop that handles all the events (new inbound datagram, new outbound bytes, new inbound bytes)
|
||||||
|
EventLoop _eventloop{};
|
||||||
|
|
||||||
|
//! Process events while specified condition is true
|
||||||
|
void _tcp_loop(const std::function<bool()> &condition);
|
||||||
|
|
||||||
|
//! Main loop of TCPConnection thread
|
||||||
|
void _tcp_main();
|
||||||
|
|
||||||
|
//! Handle to the TCPConnection thread; owner thread calls join() in the destructor
|
||||||
|
std::thread _tcp_thread{};
|
||||||
|
|
||||||
|
//! Construct LocalStreamSocket fds from socket pair, initialize eventloop
|
||||||
|
TCPSpongeSocket(std::pair<FileDescriptor, FileDescriptor> data_socket_pair, AdaptT &&datagram_interface);
|
||||||
|
|
||||||
|
std::atomic_bool _abort{false}; //!< Flag used by the owner to force the TCPConnection thread to shut down
|
||||||
|
|
||||||
|
bool _inbound_shutdown{false}; //!< Has TCPSpongeSocket shut down the incoming data to the owner?
|
||||||
|
|
||||||
|
bool _outbound_shutdown{false}; //!< Has the owner shut down the outbound data to the TCP connection?
|
||||||
|
|
||||||
|
bool _fully_acked{false}; //!< Has the outbound data been fully acknowledged by the peer?
|
||||||
|
|
||||||
|
public:
|
||||||
|
//! Construct from the interface that the TCPConnection thread will use to read and write datagrams
|
||||||
|
explicit TCPSpongeSocket(AdaptT &&datagram_interface);
|
||||||
|
|
||||||
|
//! Close socket, and wait for TCPConnection to finish
|
||||||
|
//! \note Calling this function is only advisable if the socket has reached EOF,
|
||||||
|
//! or else may wait foreever for remote peer to close the TCP connection.
|
||||||
|
void wait_until_closed();
|
||||||
|
|
||||||
|
//! Connect using the specified configurations; blocks until connect succeeds or fails
|
||||||
|
void connect(const TCPConfig &c_tcp, const FdAdapterConfig &c_ad);
|
||||||
|
|
||||||
|
//! Listen and accept using the specified configurations; blocks until accept succeeds or fails
|
||||||
|
void listen_and_accept(const TCPConfig &c_tcp, const FdAdapterConfig &c_ad);
|
||||||
|
|
||||||
|
//! When a connected socket is destructed, it will send a RST
|
||||||
|
~TCPSpongeSocket();
|
||||||
|
|
||||||
|
//! \name
|
||||||
|
//! This object cannot be safely moved or copied, since it is in use by two threads simultaneously
|
||||||
|
|
||||||
|
//!@{
|
||||||
|
TCPSpongeSocket(const TCPSpongeSocket &) = delete;
|
||||||
|
TCPSpongeSocket(TCPSpongeSocket &&) = delete;
|
||||||
|
TCPSpongeSocket &operator=(const TCPSpongeSocket &) = delete;
|
||||||
|
TCPSpongeSocket &operator=(TCPSpongeSocket &&) = delete;
|
||||||
|
//!@}
|
||||||
|
|
||||||
|
//! \name
|
||||||
|
//! Some methods of the parent Socket wouldn't work as expected on the TCP socket, so delete them
|
||||||
|
|
||||||
|
//!@{
|
||||||
|
void bind(const Address &address) = delete;
|
||||||
|
Address local_address() const = delete;
|
||||||
|
Address peer_address() const = delete;
|
||||||
|
void set_reuseaddr() = delete;
|
||||||
|
//!@}
|
||||||
|
};
|
||||||
|
|
||||||
|
using TCPOverUDPSpongeSocket = TCPSpongeSocket<TCPOverUDPSocketAdapter>;
|
||||||
|
using TCPOverIPv4SpongeSocket = TCPSpongeSocket<TCPOverIPv4OverTunFdAdapter>;
|
||||||
|
using LossyTCPOverUDPSpongeSocket = TCPSpongeSocket<LossyTCPOverUDPSocketAdapter>;
|
||||||
|
using LossyTCPOverIPv4SpongeSocket = TCPSpongeSocket<LossyTCPOverIPv4OverTunFdAdapter>;
|
||||||
|
|
||||||
|
//! \class TCPSpongeSocket
|
||||||
|
//! This class involves the simultaneous operation of two threads.
|
||||||
|
//!
|
||||||
|
//! One, the "owner" or foreground thread, interacts with this class in much the
|
||||||
|
//! same way as one would interact with a TCPSocket: it connects or listens, writes to
|
||||||
|
//! and reads from a reliable data stream, etc. Only the owner thread calls public
|
||||||
|
//! methods of this class.
|
||||||
|
//!
|
||||||
|
//! The other, the "TCPConnection" thread, takes care of the back-end tasks that the kernel would
|
||||||
|
//! perform for a TCPSocket: reading and parsing datagrams from the wire, filtering out
|
||||||
|
//! segments unrelated to the connection, etc.
|
||||||
|
//!
|
||||||
|
//! There are a few notable differences between the TCPSpongeSocket and TCPSocket interfaces:
|
||||||
|
//!
|
||||||
|
//! - a TCPSpongeSocket can only accept a single connection
|
||||||
|
//! - listen_and_accept() is a blocking function call that acts as both [listen(2)](\ref man2::listen)
|
||||||
|
//! and [accept(2)](\ref man2::accept)
|
||||||
|
//! - if TCPSpongeSocket is destructed while a TCP connection is open, the connection is
|
||||||
|
//! immediately terminated with a RST (call `wait_until_closed` to avoid this)
|
||||||
|
|
||||||
|
//! Helper class that makes a TCPOverIPv4SpongeSocket behave more like a (kernel) TCPSocket
|
||||||
|
class CS144TCPSocket : public TCPOverIPv4SpongeSocket {
|
||||||
|
public:
|
||||||
|
CS144TCPSocket();
|
||||||
|
void connect(const Address &address);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SPONGE_LIBSPONGE_TCP_SPONGE_SOCKET_HH
|
||||||
@ -2,6 +2,83 @@
|
|||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
bool TCPState::operator==(const TCPState &other) const {
|
||||||
|
return _active == other._active and _linger_after_streams_finish == other._linger_after_streams_finish and
|
||||||
|
_sender == other._sender and _receiver == other._receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TCPState::operator!=(const TCPState &other) const { return not operator==(other); }
|
||||||
|
|
||||||
|
string TCPState::name() const {
|
||||||
|
return "sender=`" + _sender + "`, receiver=`" + _receiver + "`, active=" + to_string(_active) +
|
||||||
|
", linger_after_streams_finish=" + to_string(_linger_after_streams_finish);
|
||||||
|
}
|
||||||
|
|
||||||
|
TCPState::TCPState(const TCPState::State state) {
|
||||||
|
switch (state) {
|
||||||
|
case TCPState::State::LISTEN:
|
||||||
|
_receiver = TCPReceiverStateSummary::LISTEN;
|
||||||
|
_sender = TCPSenderStateSummary::CLOSED;
|
||||||
|
break;
|
||||||
|
case TCPState::State::SYN_RCVD:
|
||||||
|
_receiver = TCPReceiverStateSummary::SYN_RECV;
|
||||||
|
_sender = TCPSenderStateSummary::SYN_SENT;
|
||||||
|
break;
|
||||||
|
case TCPState::State::SYN_SENT:
|
||||||
|
_receiver = TCPReceiverStateSummary::LISTEN;
|
||||||
|
_sender = TCPSenderStateSummary::SYN_SENT;
|
||||||
|
break;
|
||||||
|
case TCPState::State::ESTABLISHED:
|
||||||
|
_receiver = TCPReceiverStateSummary::SYN_RECV;
|
||||||
|
_sender = TCPSenderStateSummary::SYN_ACKED;
|
||||||
|
break;
|
||||||
|
case TCPState::State::CLOSE_WAIT:
|
||||||
|
_receiver = TCPReceiverStateSummary::FIN_RECV;
|
||||||
|
_sender = TCPSenderStateSummary::SYN_ACKED;
|
||||||
|
_linger_after_streams_finish = false;
|
||||||
|
break;
|
||||||
|
case TCPState::State::LAST_ACK:
|
||||||
|
_receiver = TCPReceiverStateSummary::FIN_RECV;
|
||||||
|
_sender = TCPSenderStateSummary::FIN_SENT;
|
||||||
|
_linger_after_streams_finish = false;
|
||||||
|
break;
|
||||||
|
case TCPState::State::CLOSING:
|
||||||
|
_receiver = TCPReceiverStateSummary::FIN_RECV;
|
||||||
|
_sender = TCPSenderStateSummary::FIN_SENT;
|
||||||
|
break;
|
||||||
|
case TCPState::State::FIN_WAIT_1:
|
||||||
|
_receiver = TCPReceiverStateSummary::SYN_RECV;
|
||||||
|
_sender = TCPSenderStateSummary::FIN_SENT;
|
||||||
|
break;
|
||||||
|
case TCPState::State::FIN_WAIT_2:
|
||||||
|
_receiver = TCPReceiverStateSummary::SYN_RECV;
|
||||||
|
_sender = TCPSenderStateSummary::FIN_ACKED;
|
||||||
|
break;
|
||||||
|
case TCPState::State::TIME_WAIT:
|
||||||
|
_receiver = TCPReceiverStateSummary::FIN_RECV;
|
||||||
|
_sender = TCPSenderStateSummary::FIN_ACKED;
|
||||||
|
break;
|
||||||
|
case TCPState::State::RESET:
|
||||||
|
_receiver = TCPReceiverStateSummary::ERROR;
|
||||||
|
_sender = TCPSenderStateSummary::ERROR;
|
||||||
|
_linger_after_streams_finish = false;
|
||||||
|
_active = false;
|
||||||
|
break;
|
||||||
|
case TCPState::State::CLOSED:
|
||||||
|
_receiver = TCPReceiverStateSummary::FIN_RECV;
|
||||||
|
_sender = TCPSenderStateSummary::FIN_ACKED;
|
||||||
|
_linger_after_streams_finish = false;
|
||||||
|
_active = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TCPState::TCPState(const TCPSender &sender, const TCPReceiver &receiver, const bool active, const bool linger)
|
||||||
|
: _sender(state_summary(sender))
|
||||||
|
, _receiver(state_summary(receiver))
|
||||||
|
, _active(active)
|
||||||
|
, _linger_after_streams_finish(active ? linger : false) {}
|
||||||
|
|
||||||
string TCPState::state_summary(const TCPReceiver &receiver) {
|
string TCPState::state_summary(const TCPReceiver &receiver) {
|
||||||
if (receiver.stream_out().error()) {
|
if (receiver.stream_out().error()) {
|
||||||
return TCPReceiverStateSummary::ERROR;
|
return TCPReceiverStateSummary::ERROR;
|
||||||
|
|||||||
@ -21,7 +21,41 @@
|
|||||||
//! sender/receiver states and two variables that belong to the
|
//! sender/receiver states and two variables that belong to the
|
||||||
//! overarching TCPConnection object.
|
//! overarching TCPConnection object.
|
||||||
class TCPState {
|
class TCPState {
|
||||||
|
private:
|
||||||
|
std::string _sender{};
|
||||||
|
std::string _receiver{};
|
||||||
|
bool _active{true};
|
||||||
|
bool _linger_after_streams_finish{true};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
bool operator==(const TCPState &other) const;
|
||||||
|
bool operator!=(const TCPState &other) const;
|
||||||
|
|
||||||
|
//! \brief Official state names from the [TCP](\ref rfc::rfc793) specification
|
||||||
|
enum class State {
|
||||||
|
LISTEN = 0, //!< Listening for a peer to connect
|
||||||
|
SYN_RCVD, //!< Got the peer's SYN
|
||||||
|
SYN_SENT, //!< Sent a SYN to initiate a connection
|
||||||
|
ESTABLISHED, //!< Three-way handshake complete
|
||||||
|
CLOSE_WAIT, //!< Remote side has sent a FIN, connection is half-open
|
||||||
|
LAST_ACK, //!< Local side sent a FIN from CLOSE_WAIT, waiting for ACK
|
||||||
|
FIN_WAIT_1, //!< Sent a FIN to the remote side, not yet ACK'd
|
||||||
|
FIN_WAIT_2, //!< Received an ACK for previously-sent FIN
|
||||||
|
CLOSING, //!< Received a FIN just after we sent one
|
||||||
|
TIME_WAIT, //!< Both sides have sent FIN and ACK'd, waiting for 2 MSL
|
||||||
|
CLOSED, //!< A connection that has terminated normally
|
||||||
|
RESET, //!< A connection that terminated abnormally
|
||||||
|
};
|
||||||
|
|
||||||
|
//! \brief Summarize the TCPState in a string
|
||||||
|
std::string name() const;
|
||||||
|
|
||||||
|
//! \brief Construct a TCPState given a sender, a receiver, and the TCPConnection's active and linger bits
|
||||||
|
TCPState(const TCPSender &sender, const TCPReceiver &receiver, const bool active, const bool linger);
|
||||||
|
|
||||||
|
//! \brief Construct a TCPState that corresponds to one of the "official" TCP state names
|
||||||
|
TCPState(const TCPState::State state);
|
||||||
|
|
||||||
//! \brief Summarize the state of a TCPReceiver in a string
|
//! \brief Summarize the state of a TCPReceiver in a string
|
||||||
static std::string state_summary(const TCPReceiver &receiver);
|
static std::string state_summary(const TCPReceiver &receiver);
|
||||||
|
|
||||||
|
|||||||
6
libsponge/tcp_helpers/tuntap_adapter.cc
Normal file
6
libsponge/tcp_helpers/tuntap_adapter.cc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#include "tuntap_adapter.hh"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
//! Specialize LossyFdAdapter to TCPOverIPv4OverTunFdAdapter
|
||||||
|
template class LossyFdAdapter<TCPOverIPv4OverTunFdAdapter>;
|
||||||
42
libsponge/tcp_helpers/tuntap_adapter.hh
Normal file
42
libsponge/tcp_helpers/tuntap_adapter.hh
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#ifndef SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH
|
||||||
|
#define SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH
|
||||||
|
|
||||||
|
#include "tcp_over_ip.hh"
|
||||||
|
#include "tun.hh"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
//! \brief A FD adapter for IPv4 datagrams read from and written to a TUN device
|
||||||
|
class TCPOverIPv4OverTunFdAdapter : public TCPOverIPv4Adapter {
|
||||||
|
private:
|
||||||
|
TunFD _tun;
|
||||||
|
|
||||||
|
public:
|
||||||
|
//! Construct from a TunFD
|
||||||
|
explicit TCPOverIPv4OverTunFdAdapter(TunFD &&tun) : _tun(std::move(tun)) {}
|
||||||
|
|
||||||
|
//! Attempts to read and parse an IPv4 datagram containing a TCP segment related to the current connection
|
||||||
|
std::optional<TCPSegment> read() {
|
||||||
|
InternetDatagram ip_dgram;
|
||||||
|
if (ip_dgram.parse(_tun.read()) != ParseResult::NoError) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return unwrap_tcp_in_ip(ip_dgram);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Creates an IPv4 datagram from a TCP segment and writes it to the TUN device
|
||||||
|
void write(TCPSegment &seg) { _tun.write(wrap_tcp_in_ip(seg).serialize()); }
|
||||||
|
|
||||||
|
//! Access the underlying TUN device
|
||||||
|
operator TunFD &() { return _tun; }
|
||||||
|
|
||||||
|
//! Access the underlying TUN device
|
||||||
|
operator const TunFD &() const { return _tun; }
|
||||||
|
};
|
||||||
|
|
||||||
|
//! Typedef for TCPOverIPv4OverTunFdAdapter
|
||||||
|
using LossyTCPOverIPv4OverTunFdAdapter = LossyFdAdapter<TCPOverIPv4OverTunFdAdapter>;
|
||||||
|
|
||||||
|
#endif // SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH
|
||||||
@ -1,4 +1,4 @@
|
|||||||
add_library (spongechecks STATIC byte_stream_test_harness.cc)
|
add_library (spongechecks STATIC send_equivalence_checker.cc tcp_fsm_test_harness.cc byte_stream_test_harness.cc)
|
||||||
|
|
||||||
macro (add_test_exec exec_name)
|
macro (add_test_exec exec_name)
|
||||||
add_executable ("${exec_name}" "${exec_name}.cc")
|
add_executable ("${exec_name}" "${exec_name}.cc")
|
||||||
@ -7,6 +7,12 @@ macro (add_test_exec exec_name)
|
|||||||
endmacro (add_test_exec)
|
endmacro (add_test_exec)
|
||||||
|
|
||||||
add_test_exec (tcp_parser ${LIBPCAP})
|
add_test_exec (tcp_parser ${LIBPCAP})
|
||||||
|
add_test_exec (ipv4_parser ${LIBPCAP})
|
||||||
|
add_test_exec (fsm_active_close)
|
||||||
|
add_test_exec (fsm_passive_close)
|
||||||
|
add_test_exec (fsm_ack_rst_relaxed)
|
||||||
|
add_test_exec (fsm_ack_rst_win_relaxed)
|
||||||
|
add_test_exec (fsm_stream_reassembler_cap)
|
||||||
add_test_exec (fsm_stream_reassembler_single)
|
add_test_exec (fsm_stream_reassembler_single)
|
||||||
add_test_exec (fsm_stream_reassembler_seq)
|
add_test_exec (fsm_stream_reassembler_seq)
|
||||||
add_test_exec (fsm_stream_reassembler_dup)
|
add_test_exec (fsm_stream_reassembler_dup)
|
||||||
@ -14,16 +20,23 @@ add_test_exec (fsm_stream_reassembler_holes)
|
|||||||
add_test_exec (fsm_stream_reassembler_many)
|
add_test_exec (fsm_stream_reassembler_many)
|
||||||
add_test_exec (fsm_stream_reassembler_overlapping)
|
add_test_exec (fsm_stream_reassembler_overlapping)
|
||||||
add_test_exec (fsm_stream_reassembler_win)
|
add_test_exec (fsm_stream_reassembler_win)
|
||||||
add_test_exec (fsm_stream_reassembler_cap)
|
add_test_exec (fsm_connect_relaxed)
|
||||||
|
add_test_exec (fsm_listen_relaxed)
|
||||||
|
add_test_exec (fsm_reorder)
|
||||||
|
add_test_exec (fsm_loopback)
|
||||||
|
add_test_exec (fsm_loopback_win)
|
||||||
|
add_test_exec (fsm_retx_relaxed)
|
||||||
|
add_test_exec (fsm_retx_win)
|
||||||
|
add_test_exec (fsm_winsize)
|
||||||
|
add_test_exec (wrapping_integers_cmp)
|
||||||
|
add_test_exec (wrapping_integers_unwrap)
|
||||||
|
add_test_exec (wrapping_integers_wrap)
|
||||||
|
add_test_exec (wrapping_integers_roundtrip)
|
||||||
add_test_exec (byte_stream_construction)
|
add_test_exec (byte_stream_construction)
|
||||||
add_test_exec (byte_stream_one_write)
|
add_test_exec (byte_stream_one_write)
|
||||||
add_test_exec (byte_stream_two_writes)
|
add_test_exec (byte_stream_two_writes)
|
||||||
add_test_exec (byte_stream_capacity)
|
add_test_exec (byte_stream_capacity)
|
||||||
add_test_exec (byte_stream_many_writes)
|
add_test_exec (byte_stream_many_writes)
|
||||||
add_test_exec (wrapping_integers_cmp)
|
|
||||||
add_test_exec (wrapping_integers_unwrap)
|
|
||||||
add_test_exec (wrapping_integers_wrap)
|
|
||||||
add_test_exec (wrapping_integers_roundtrip)
|
|
||||||
add_test_exec (recv_connect)
|
add_test_exec (recv_connect)
|
||||||
add_test_exec (recv_transmit)
|
add_test_exec (recv_transmit)
|
||||||
add_test_exec (recv_window)
|
add_test_exec (recv_window)
|
||||||
|
|||||||
BIN
tests/ipv4_parser.data
Normal file
BIN
tests/ipv4_parser.data
Normal file
Binary file not shown.
111
tun.sh
Executable file
111
tun.sh
Executable file
@ -0,0 +1,111 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
show_usage () {
|
||||||
|
echo "Usage: $0 <start | stop | restart | check> [tunnum ...]"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
start_tun () {
|
||||||
|
local TUNNUM="$1" TUNDEV="tun$1"
|
||||||
|
ip tuntap add mode tun user "${SUDO_USER}" name "${TUNDEV}"
|
||||||
|
ip addr add "${TUN_IP_PREFIX}.${TUNNUM}.1/24" dev "${TUNDEV}"
|
||||||
|
ip link set dev "${TUNDEV}" up
|
||||||
|
ip route change "${TUN_IP_PREFIX}.${TUNNUM}.0/24" dev "${TUNDEV}" rto_min 10ms
|
||||||
|
|
||||||
|
# Apply NAT (masquerading) only to traffic from CS144's network devices
|
||||||
|
iptables -t nat -A PREROUTING -s ${TUN_IP_PREFIX}.${TUNNUM}.0/24 -j CONNMARK --set-mark ${TUNNUM}
|
||||||
|
iptables -t nat -A POSTROUTING -j MASQUERADE -m connmark --mark ${TUNNUM}
|
||||||
|
echo 1 > /proc/sys/net/ipv4/ip_forward
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_tun () {
|
||||||
|
local TUNDEV="tun$1"
|
||||||
|
iptables -t nat -D PREROUTING -s ${TUN_IP_PREFIX}.${1}.0/24 -j CONNMARK --set-mark ${1}
|
||||||
|
iptables -t nat -D POSTROUTING -j MASQUERADE -m connmark --mark ${1}
|
||||||
|
ip tuntap del mode tun name "$TUNDEV"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_all () {
|
||||||
|
while [ ! -z "$1" ]; do
|
||||||
|
local INTF="$1"; shift
|
||||||
|
start_tun "$INTF"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_all () {
|
||||||
|
while [ ! -z "$1" ]; do
|
||||||
|
local INTF="$1"; shift
|
||||||
|
stop_tun "$INTF"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_all() {
|
||||||
|
stop_all "$@"
|
||||||
|
start_all "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_tun () {
|
||||||
|
[ "$#" != 1 ] && { echo "bad params in check_tun"; exit 1; }
|
||||||
|
local TUNDEV="tun${1}"
|
||||||
|
# make sure tun is healthy: device is up, ip_forward is set, and iptables is configured
|
||||||
|
ip link show ${TUNDEV} &>/dev/null || return 1
|
||||||
|
[ "$(cat /proc/sys/net/ipv4/ip_forward)" = "1" ] || return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
check_sudo () {
|
||||||
|
if [ "$SUDO_USER" = "root" ]; then
|
||||||
|
echo "please execute this script as a regular user, not as root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "$SUDO_USER" ]; then
|
||||||
|
# if the user didn't call us with sudo, re-execute
|
||||||
|
exec sudo $0 "$MODE" "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# check arguments
|
||||||
|
if [ -z "$1" ] || ([ "$1" != "start" ] && [ "$1" != "stop" ] && [ "$1" != "restart" ] && [ "$1" != "check" ]); then
|
||||||
|
show_usage
|
||||||
|
fi
|
||||||
|
MODE=$1; shift
|
||||||
|
|
||||||
|
# set default argument
|
||||||
|
if [ "$#" = "0" ]; then
|
||||||
|
set -- 144 145
|
||||||
|
fi
|
||||||
|
|
||||||
|
# execute 'check' before trying to sudo
|
||||||
|
# - like start, but exit successfully if everything is OK
|
||||||
|
if [ "$MODE" = "check" ]; then
|
||||||
|
declare -a INTFS
|
||||||
|
MODE="start"
|
||||||
|
while [ ! -z "$1" ]; do
|
||||||
|
INTF="$1"; shift
|
||||||
|
check_tun ${INTF}
|
||||||
|
RET=$?
|
||||||
|
if [ "$RET" = "0" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$((RET > 1))" = "1" ]; then
|
||||||
|
MODE="restart"
|
||||||
|
fi
|
||||||
|
INTFS+=($INTF)
|
||||||
|
done
|
||||||
|
|
||||||
|
# address only the interfaces that need it
|
||||||
|
set -- "${INTFS[@]}"
|
||||||
|
if [ "$#" = "0" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo -e "[$0] Bringing up tunnels ${INTFS[@]}:"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# sudo if necessary
|
||||||
|
check_sudo "$@"
|
||||||
|
|
||||||
|
# get configuration
|
||||||
|
. "$(dirname "$0")"/etc/tunconfig
|
||||||
|
|
||||||
|
# start, stop, or restart all intfs
|
||||||
|
eval "${MODE}_all" "$@"
|
||||||
238
txrx.sh
Executable file
238
txrx.sh
Executable file
@ -0,0 +1,238 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
show_usage() {
|
||||||
|
echo "Usage: $0 <-i|-u> <-c|-s> <-R|-S|-D> [-n|-o]"
|
||||||
|
echo " [-t <rtto>] [-d <size>] [-w <size>] [-l <rate>] [-L <rate>]"
|
||||||
|
echo
|
||||||
|
echo " Option Default"
|
||||||
|
echo " -- --"
|
||||||
|
echo " -i or -u Select IP or UDP mode (must specify)"
|
||||||
|
echo " -c or -s Select client or server mode (must specify)"
|
||||||
|
echo " -R, -S, -D Receive test, Send test, or Duplex test (must specify)"
|
||||||
|
echo
|
||||||
|
echo " -t <rtto> Set rtto to <rtto> ms 12"
|
||||||
|
echo " -d <size> Set total transfer size to <size> 32"
|
||||||
|
echo " -w <size> Set window size to <size> 1452"
|
||||||
|
echo
|
||||||
|
echo " -l <rate> Set downlink loss to <rate> (float in 0..1) 0"
|
||||||
|
echo " -L <rate> Set uplink loss to <rate> (float in 0..1) 0"
|
||||||
|
echo
|
||||||
|
echo " -n In IP mode, use tcp_native rather tcp_ipv4_ref False"
|
||||||
|
echo " -o In IP mode, use socat rather than tcp_ipv4_ref False"
|
||||||
|
[ ! -z "$1" ] && { echo; echo ERROR: "$1"; }
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_cmdline_options () {
|
||||||
|
# prepare to use getopts
|
||||||
|
local OPT= OPTIND=1 OPTARG=
|
||||||
|
CSMODE= RSDMODE= DATASIZE=32 WINSIZE= IUMODE= USE_IPV4= RTTO="-t 12" LOSS_UP= LOSS_DN=
|
||||||
|
while getopts "t:oniucsRSDd:w:p:l:L:" OPT; do
|
||||||
|
case "$OPT" in
|
||||||
|
i|u)
|
||||||
|
[ ! -z "$IUMODE" ] && show_usage "Only one of -i and -u is allowed."
|
||||||
|
IUMODE=$OPT
|
||||||
|
;;
|
||||||
|
c|s)
|
||||||
|
[ ! -z "$CSMODE" ] && show_usage "Only one of -c and -s is allowed."
|
||||||
|
CSMODE=$OPT
|
||||||
|
;;
|
||||||
|
R|S|D)
|
||||||
|
[ ! -z "$RSDMODE" ] && show_usage "Only one of -R, -S, and -D is allowed."
|
||||||
|
RSDMODE=$OPT
|
||||||
|
;;
|
||||||
|
d)
|
||||||
|
DATASIZE="$OPTARG"
|
||||||
|
;;
|
||||||
|
w)
|
||||||
|
expand_num "$OPTARG" || show_usage "Bad numeric arg \"$OPTARG\" to -w."
|
||||||
|
WINSIZE="-w ${NUM_EXPANDED}"
|
||||||
|
;;
|
||||||
|
l)
|
||||||
|
LOSS_DN="$OPTARG"
|
||||||
|
;;
|
||||||
|
L)
|
||||||
|
LOSS_UP="$OPTARG"
|
||||||
|
;;
|
||||||
|
n|o)
|
||||||
|
[ ! -z "$USE_IPV4" ] && show_usage "Only one of -n and -o is allowed."
|
||||||
|
USE_IPV4=$OPT
|
||||||
|
;;
|
||||||
|
t)
|
||||||
|
expand_num "$OPTARG" || show_usage "Bad numeric arg \"$OPTARG\" to -t."
|
||||||
|
RTTO="-t ${NUM_EXPANDED}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_usage "Unknown option $OPT"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
if [ "$OPTIND" != $(($# + 1)) ]; then
|
||||||
|
show_usage "Extraneous arguments detected."
|
||||||
|
fi
|
||||||
|
if [ -z "$CSMODE" ] || [ -z "$RSDMODE" ] || [ -z "$IUMODE" ]; then
|
||||||
|
show_usage "You must specify either -i or -u, either -c or -s, and one of -R, -S, and -D."
|
||||||
|
fi
|
||||||
|
if [ ! -z "$USE_IPV4" ] && [ "$IUMODE" != "i" ]; then
|
||||||
|
show_usage "-n and -o may only be specified in IP mode (-i)."
|
||||||
|
fi
|
||||||
|
# loss param args depend on whether we're applying to ref or test program (test uplink == loss downlink)
|
||||||
|
{ [ ! -z "$USE_IPV4" ] && local LD_SWITCH="-Ld" LU_SWITCH="-Lu"; } || local LD_SWITCH="-Lu" LU_SWITCH="-Ld"
|
||||||
|
[ ! -z "$LOSS_DN" ] && LOSS_DN="${LD_SWITCH} ${LOSS_DN}"
|
||||||
|
[ ! -z "$LOSS_UP" ] && LOSS_UP="${LU_SWITCH} ${LOSS_UP}"
|
||||||
|
}
|
||||||
|
|
||||||
|
expand_num () {
|
||||||
|
[ "$#" != "1" ] && { echo "bad args"; exit 1; }
|
||||||
|
NUM_EXPANDED=$(numfmt --from=iec "$1" 2>/dev/null)
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
_socat_listen () {
|
||||||
|
coproc socat tcp4-listen:${SERVER_PORT},reuseaddr,reuseport,linger=2 stdio >"$1" <"$2" && sleep 0.1
|
||||||
|
set +u
|
||||||
|
[ -z "$COPROC_PID" ] && { echo "Error in _socat_listen"; exit 1; }
|
||||||
|
set -u
|
||||||
|
}
|
||||||
|
|
||||||
|
_socat_connect () {
|
||||||
|
socat tcp4-connect:${TEST_HOST}:${SERVER_PORT},reuseaddr,reuseport,linger=2 stdio >"$1" <"$2" ||
|
||||||
|
{ echo "Error in _socat_connect"; exit 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
_rt_listen () {
|
||||||
|
coproc $3 -l $4 ${SERVER_PORT} >"$1" <"$2" && sleep 0.1
|
||||||
|
set +u
|
||||||
|
[ -z "$COPROC_PID" ] && { echo "Error in _rt_listen"; exit 1; }
|
||||||
|
set -u
|
||||||
|
}
|
||||||
|
|
||||||
|
_rt_connect () {
|
||||||
|
$3 $4 ${SERVER_PORT} >"$1" <"$2" || { echo "Error in _rt_connect"; exit 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
test_listen () {
|
||||||
|
[ "$#" != 2 ] && { echo "bad args"; exit 1; }
|
||||||
|
_rt_listen "$1" "$2" "${TEST_PROG}" "${TEST_HOST}"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_connect () {
|
||||||
|
[ "$#" != 2 ] && { echo "bad args"; exit 1; }
|
||||||
|
_rt_connect "$1" "$2" "${TEST_PROG}" "${REF_HOST}"
|
||||||
|
}
|
||||||
|
|
||||||
|
ref_listen () {
|
||||||
|
[ "$#" != 2 ] && { echo "bad args"; exit 1; }
|
||||||
|
if [ "$IUMODE" = "u" ] || [ -z "$USE_IPV4" ] || [ "$USE_IPV4" = "n" ]; then
|
||||||
|
_rt_listen "$1" "$2" "${REF_PROG}" "${REF_HOST}"
|
||||||
|
else
|
||||||
|
_socat_listen "$1" "$2"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ref_connect () {
|
||||||
|
[ "$#" != 2 ] && { echo "bad args"; exit 1; }
|
||||||
|
if [ "$IUMODE" = "u" ] || [ -z "$USE_IPV4" ] || [ "$USE_IPV4" = "n" ]; then
|
||||||
|
_rt_connect "$1" "$2" "${REF_PROG}" "${TEST_HOST}"
|
||||||
|
else
|
||||||
|
_socat_connect "$1" "$2"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
hash_file () {
|
||||||
|
[ "$#" != "1" ] && { echo "bad args"; exit 1; }
|
||||||
|
sha256sum "$1" | cut -d \ -f 1
|
||||||
|
}
|
||||||
|
|
||||||
|
make_test_file () {
|
||||||
|
if expand_num "$2"; then
|
||||||
|
dd status=none if=/dev/urandom of="$1" bs="${NUM_EXPANDED}" count=1 || { echo "Failed to make test file."; exit 1; }
|
||||||
|
else
|
||||||
|
# can't interpret as a number, so interpret as literal data to send
|
||||||
|
echo -en "$2" >"$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
exit_cleanup () {
|
||||||
|
set +u
|
||||||
|
rm -f "${TEST_IN_FILE}" "${TEST_OUT_FILE}" "${TEST_OUT2_FILE}"
|
||||||
|
[ ! -z "$COPROC_PID" ] && kill ${COPROC_PID}
|
||||||
|
}
|
||||||
|
|
||||||
|
# make sure tun device is running
|
||||||
|
ip link show tun144 &>/dev/null || { echo "please enable tun144 and re-run"; exit 1; }
|
||||||
|
ip link show tun145 &>/dev/null || { echo "please enable tun145 and re-run"; exit 1; }
|
||||||
|
|
||||||
|
set -u
|
||||||
|
trap exit_cleanup EXIT
|
||||||
|
|
||||||
|
get_cmdline_options "$@"
|
||||||
|
|
||||||
|
. "$(dirname "$0")"/etc/tunconfig
|
||||||
|
REF_HOST=${TUN_IP_PREFIX}.144.1
|
||||||
|
TEST_HOST=${TUN_IP_PREFIX}.144.1
|
||||||
|
SERVER_PORT=$(($((RANDOM % 50000)) + 1025))
|
||||||
|
if [ "$IUMODE" = "i" ]; then
|
||||||
|
# IPv4 mode
|
||||||
|
TEST_HOST=${TUN_IP_PREFIX}.144.9
|
||||||
|
if [ -z "$USE_IPV4" ]; then
|
||||||
|
REF_HOST=${TUN_IP_PREFIX}.145.9
|
||||||
|
REF_PROG="./apps/tcp_ipv4 ${RTTO} ${WINSIZE} ${LOSS_UP} ${LOSS_DN} -d tun145 -a ${REF_HOST}"
|
||||||
|
TEST_PROG="./apps/tcp_ipv4 ${RTTO} ${WINSIZE} -d tun144 -a ${TEST_HOST}"
|
||||||
|
else
|
||||||
|
REF_PROG="./apps/tcp_native"
|
||||||
|
TEST_PROG="./apps/tcp_ipv4 ${RTTO} ${WINSIZE} ${LOSS_UP} ${LOSS_DN} -d tun144 -a ${TEST_HOST}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# UDP mode
|
||||||
|
REF_PROG="./apps/tcp_udp ${RTTO} ${WINSIZE} ${LOSS_UP} ${LOSS_DN}"
|
||||||
|
TEST_PROG="./apps/tcp_udp ${RTTO} ${WINSIZE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TEST_OUT_FILE=$(mktemp)
|
||||||
|
TEST_IN_FILE=$(mktemp)
|
||||||
|
make_test_file "${TEST_IN_FILE}" "${DATASIZE}"
|
||||||
|
HASH_IN=$(sha256sum ${TEST_IN_FILE} | cut -d \ -f 1)
|
||||||
|
HASH_OUT2=
|
||||||
|
case "$RSDMODE" in
|
||||||
|
S) # test sending
|
||||||
|
if [ "$CSMODE" = "c" ]; then
|
||||||
|
ref_listen "${TEST_OUT_FILE}" /dev/null
|
||||||
|
test_connect /dev/null "${TEST_IN_FILE}"
|
||||||
|
else
|
||||||
|
test_listen /dev/null "${TEST_IN_FILE}"
|
||||||
|
ref_connect "${TEST_OUT_FILE}" /dev/null
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
R) # test receiving
|
||||||
|
if [ "$CSMODE" = "c" ]; then
|
||||||
|
ref_listen /dev/null "${TEST_IN_FILE}"
|
||||||
|
test_connect "${TEST_OUT_FILE}" /dev/null
|
||||||
|
else
|
||||||
|
test_listen "${TEST_OUT_FILE}" /dev/null
|
||||||
|
ref_connect /dev/null "${TEST_IN_FILE}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
D) # test full-duplex
|
||||||
|
TEST_OUT2_FILE=$(mktemp)
|
||||||
|
if [ "$CSMODE" = "c" ]; then
|
||||||
|
ref_listen "${TEST_OUT_FILE}" "${TEST_IN_FILE}"
|
||||||
|
test_connect "${TEST_OUT2_FILE}" "${TEST_IN_FILE}"
|
||||||
|
else
|
||||||
|
test_listen "${TEST_OUT_FILE}" "${TEST_IN_FILE}"
|
||||||
|
ref_connect "${TEST_OUT2_FILE}" "${TEST_IN_FILE}"
|
||||||
|
fi
|
||||||
|
HASH_OUT2=$(hash_file "${TEST_OUT2_FILE}")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if ! wait; then
|
||||||
|
echo ERROR: subprocess failed
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
HASH_OUT=$(hash_file ${TEST_OUT_FILE})
|
||||||
|
if [ ! -z "${HASH_OUT2}" ] && [ "${HASH_OUT}" != "${HASH_OUT2}" ] || [ "${HASH_IN}" != "${HASH_OUT}" ]; then
|
||||||
|
echo ERROR: "$HASH_IN" neq "$HASH_OUT" or "$HASH_OUT2"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
29
writeups/lab4.md
Normal file
29
writeups/lab4.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
Lab 4 Writeup
|
||||||
|
=============
|
||||||
|
|
||||||
|
My name: [your name here]
|
||||||
|
|
||||||
|
My SUNet ID: [your sunetid here]
|
||||||
|
|
||||||
|
I collaborated with: [list sunetids here]
|
||||||
|
|
||||||
|
I would like to thank/reward these classmates for their help: [list sunetids here]
|
||||||
|
|
||||||
|
This lab took me about [n] hours to do. I [did/did not] attend the lab session.
|
||||||
|
|
||||||
|
Program Structure and Design of the TCPConnection:
|
||||||
|
[]
|
||||||
|
|
||||||
|
Implementation Challenges:
|
||||||
|
[]
|
||||||
|
|
||||||
|
Remaining Bugs:
|
||||||
|
[]
|
||||||
|
|
||||||
|
- Optional: I had unexpected difficulty with: [describe]
|
||||||
|
|
||||||
|
- Optional: I think you could make this lab better by: [describe]
|
||||||
|
|
||||||
|
- Optional: I was surprised by: [describe]
|
||||||
|
|
||||||
|
- Optional: I'm not sure about: [describe]
|
||||||
Loading…
Reference in New Issue
Block a user