Commit 32abbda9 authored by zhouqi's avatar zhouqi

init class 05

parents
---
Language: Cpp
BasedOnStyle: LLVM
AllowAllParametersOfDeclarationOnNextLine: false
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BreakConstructorInitializers: BeforeComma
ColumnLimit: 120
CommentPragmas: '^(!|NOLINT)'
ConstructorInitializerAllOnOneLineOrOnePerLine: true
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(_dt|_win)?$'
IndentCaseLabels: true
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
PenaltyReturnTypeOnItsOwnLine: 200
SpacesBeforeTrailingComments: 2
TabWidth: 4
UseTab: Never
...
*.a
*.o
/build
CMakeCache.txt
CMakeFiles
Makefile
cmake_install.cmake
CTestTestfile.cmake
compile_commands.json
cmake_minimum_required (VERSION 2.8.5)
cmake_policy (SET CMP0054 NEW)
project (Sponge)
include (etc/build_defs.cmake)
include (etc/build_type.cmake)
include (etc/cflags.cmake)
include (etc/doxygen.cmake)
include (etc/clang_format.cmake)
include (etc/clang_tidy.cmake)
include (etc/cppcheck.cmake)
include_directories ("${PROJECT_SOURCE_DIR}/libsponge/util")
include_directories ("${PROJECT_SOURCE_DIR}/libsponge/tcp_helpers")
include_directories ("${PROJECT_SOURCE_DIR}/libsponge")
add_subdirectory ("${PROJECT_SOURCE_DIR}/libsponge")
add_subdirectory ("${PROJECT_SOURCE_DIR}/apps")
add_subdirectory ("${PROJECT_SOURCE_DIR}/tests")
add_subdirectory ("${PROJECT_SOURCE_DIR}/doctests")
include (etc/tests.cmake)
For build prereqs, see [the CS144 VM setup instructions](https://web.stanford.edu/class/cs144/vm_howto).
## Sponge quickstart
To set up your build directory:
$ mkdir -p <path/to/sponge>/build
$ cd <path/to/sponge>/build
$ cmake ..
**Note:** all further commands listed below should be run from the `build` dir.
To build:
$ make
You can use the `-j` switch to build in parallel, e.g.,
$ make -j$(nproc)
To test (after building; make sure you've got the [build prereqs](https://web.stanford.edu/class/cs144/vm_howto) installed!)
$ make check
The first time you run `make check`, it will run `sudo` to configure two
[TUN](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) devices for use during
testing.
### build options
You can specify a different compiler when you run cmake:
$ CC=clang CXX=clang++ cmake ..
You can also specify `CLANG_TIDY=` or `CLANG_FORMAT=` (see "other useful targets", below).
Sponge's build system supports several different build targets. By default, cmake chooses the `Release`
target, which enables the usual optimizations. The `Debug` target enables debugging and reduces the
level of optimization. To choose the `Debug` target:
$ cmake .. -DCMAKE_BUILD_TYPE=Debug
The following targets are supported:
- `Release` - optimizations
- `Debug` - debug symbols and `-Og`
- `RelASan` - release build with [ASan](https://en.wikipedia.org/wiki/AddressSanitizer) and
[UBSan](https://developers.redhat.com/blog/2014/10/16/gcc-undefined-behavior-sanitizer-ubsan/)
- `RelTSan` - release build with
[ThreadSan](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Thread_Sanitizer)
- `DebugASan` - debug build with ASan and UBSan
- `DebugTSan` - debug build with ThreadSan
Of course, you can combine all of the above, e.g.,
$ CLANG_TIDY=clang-tidy-6.0 CXX=clang++-6.0 .. -DCMAKE_BUILD_TYPE=Debug
**Note:** if you want to change `CC`, `CXX`, `CLANG_TIDY`, or `CLANG_FORMAT`, you need to remove
`build/CMakeCache.txt` and re-run cmake. (This isn't necessary for `CMAKE_BUILD_TYPE`.)
### other useful targets
To generate documentation (you'll need `doxygen`; output will be in `build/doc/`):
$ make doc
To lint (you'll need `clang-tidy`):
$ make -j$(nproc) tidy
To run cppcheck (you'll need `cppcheck`):
$ make cppcheck
To format (you'll need `clang-format`):
$ make format
To see all available targets,
$ make help
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 (tcp_benchmark)
#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); },
[&] { _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.set_error(); });
// 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); },
[&] { _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.set_error(); });
// loop until completion
while (true) {
if (EventLoop::Result::Exit == _eventloop.wait_next_event(-1)) {
return;
}
}
}
#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
#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;
}
#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 = "0";
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(TunFD(tun_dev_name == nullptr ? TUN_DFLT : tun_dev_name));
deque<string> write_queue;
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;
}
#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;
}
#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(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;
}
#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;
}
#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;
}
#include "socket.hh"
#include "util.hh"
#include <cstdlib>
#include <iostream>
using namespace std;
void get_URL(const string &host, const string &path) {
// Your code here.
// You will need to connect to the "http" service on
// the computer whose name is in the "host" string,
// then request the URL path given in the "path" string.
// Then you'll need to print out everything the server sends back,
// (not just one call to read() -- everything) until you reach
// the "eof" (end of file).
cerr << "Function called: get_URL(" << host << ", " << path << ").\n";
cerr << "Warning: get_URL() has not been implemented yet.\n";
}
int main(int argc, char *argv[]) {
try {
if (argc <= 0) {
abort(); // For sticklers: don't try to access argv[0] if argc <= 0.
}
// The program takes two command-line arguments: the hostname and "path" part of the URL.
// Print the usage message unless there are these two arguments (plus the program name
// itself, so arg count = 3 in total).
if (argc != 3) {
cerr << "Usage: " << argv[0] << " HOST PATH\n";
cerr << "\tExample: " << argv[0] << " stanford.edu /class/cs144\n";
return EXIT_FAILURE;
}
// Get the command-line arguments.
const string host = argv[1];
const string path = argv[2];
// Call the student-written function.
get_URL(host, path);
} catch (const exception &e) {
cerr << e.what() << "\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
add_sponge_exec (address_dt)
add_sponge_exec (parser_dt)
add_sponge_exec (socket_dt)
#include "address.hh"
#include <cstdlib>
#include <iostream>
#include <stdexcept>
int main() {
try {
#include "address_example_1.cc"
#include "address_example_2.cc"
#include "address_example_3.cc"
if ((google_webserver.port() != 443) || (a_dns_server_numeric != 0x12'47'00'97)) {
throw std::runtime_error("unexpected value");
}
} catch (const std::exception &e) {
std::cerr << "This test requires Internet access and working DNS.\n";
std::cerr << "Error: " << e.what() << "\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
const Address google_webserver("www.google.com", "https");
const Address a_dns_server("18.71.0.151", 53);
const uint32_t a_dns_server_numeric = a_dns_server.ipv4_numeric();
#include "parser.hh"
#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <vector>
int main() {
try {
#include "parser_example.cc"
} catch (...) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
const uint32_t val1 = 0xdeadbeef;
const uint16_t val2 = 0xc0c0;
const uint8_t val3 = 0xff;
const uint32_t val4 = 0x0c05fefe;
// first, let's serialize it
std::string buffer;
buffer.push_back(0x32); // manually added to beginning of string
{
NetUnparser p;
p.u32(buffer, val1);
p.u16(buffer, val2);
p.u8(buffer, val3);
p.u32(buffer, val4);
} // p goes out of scope, data is in buffer
// now let's deserialize it
uint8_t out0, out3;
uint32_t out1, out4;
uint16_t out2;
{
NetParser p{std::string(buffer)}; // NOTE: starting at offset 0
out0 = p.u8(); // buffer[0], which we manually set to 0x32 above
out1 = p.u32(); // parse out val1
out2 = p.u16(); // val2
out3 = p.u8(); // val3
out4 = p.u32(); // val4
} // p goes out of scope
if (out0 != 0x32 || out1 != val1 || out2 != val2 || out3 != val3 || out4 != val4) {
throw std::runtime_error("bad parse");
}
#include "socket.hh"
#include "address.hh"
#include "util.hh"
#include <array>
#include <cstdlib>
#include <random>
#include <stdexcept>
#include <sys/socket.h>
#include <vector>
int main() {
try {
{
#include "socket_example_1.cc"
} {
#include "socket_example_2.cc"
} {
#include "socket_example_3.cc"
}
} catch (...) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
const uint16_t portnum = ((std::random_device()()) % 50000) + 1025;
// create a UDP socket and bind it to a local address
UDPSocket sock1{};
sock1.bind(Address("127.0.0.1", portnum));
// create another UDP socket and send a datagram to the first socket without connecting
UDPSocket sock2{};
sock2.sendto(Address("127.0.0.1", portnum), "hi there");
// receive sent datagram, connect the socket to the peer's address, and send a response
auto recvd = sock1.recv();
sock1.connect(recvd.source_address);
sock1.send("hi yourself");
auto recvd2 = sock2.recv();
if (recvd.payload != "hi there" || recvd2.payload != "hi yourself") {
throw std::runtime_error("wrong data received");
}
const uint16_t portnum = ((std::random_device()()) % 50000) + 1025;
// create a TCP socket, bind it to a local address, and listen
TCPSocket sock1{};
sock1.bind(Address("127.0.0.1", portnum));
sock1.listen(1);
// create another socket and connect to the first one
TCPSocket sock2{};
sock2.connect(Address("127.0.0.1", portnum));
// accept the connection
auto sock3 = sock1.accept();
sock3.write("hi there");
auto recvd = sock2.read();
sock2.write("hi yourself");
auto recvd2 = sock3.read();
sock1.close(); // don't need to accept any more connections
sock2.close(); // you can call close(2) on a socket
sock3.shutdown(SHUT_RDWR); // you can also shutdown(2) a socket
if (recvd != "hi there" || recvd2 != "hi yourself") {
throw std::runtime_error("wrong data received");
}
// create a pair of stream sockets
std::array<int, 2> fds{};
SystemCall("socketpair", ::socketpair(AF_UNIX, SOCK_STREAM, 0, fds.data()));
LocalStreamSocket pipe1{FileDescriptor(fds[0])}, pipe2{FileDescriptor(fds[1])};
pipe1.write("hi there");
auto recvd = pipe2.read();
pipe2.write("hi yourself");
auto recvd2 = pipe1.read();
if (recvd != "hi there" || recvd2 != "hi yourself") {
throw std::runtime_error("wrong data received");
}
# Doxyfile 1.8.14
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "Sponge"
PROJECT_BRIEF = "CS144's user-space TCP library"
PROJECT_LOGO = "@PROJECT_SOURCE_DIR@/etc/sponge_small.png"
INPUT = @PROJECT_SOURCE_DIR@
RECURSIVE = YES
EXCLUDE = @PROJECT_SOURCE_DIR@/etc @PROJECT_SOURCE_DIR@/build @PROJECT_SOURCE_DIR@/tests @PROJECT_SOURCE_DIR@/writeups
OUTPUT_DIRECTORY = "@PROJECT_BINARY_DIR@/doc"
CASE_SENSE_NAMES = NO
SORT_BRIEF_DOCS = YES
SORT_MEMBERS_CTORS_1ST = YES
SHOW_NAMESPACES = NO
USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md
SOURCE_BROWSER = YES
EXT_LINKS_IN_WINDOW = YES
INCLUDE_PATH = @PROJECT_SOURCE_DIR@/libsponge
TAGFILES = "@PROJECT_SOURCE_DIR@/etc/cppreference-doxygen-web.tag.xml=https://en.cppreference.com/w/"
TAGFILES += "@PROJECT_SOURCE_DIR@/etc/linux-man-doxygen-web.tag.xml=http://man7.org/linux/man-pages/"
TAGFILES += "@PROJECT_SOURCE_DIR@/etc/rfc-doxygen-web.tag.xml=https://tools.ietf.org/html/"
HIDE_UNDOC_RELATIONS = NO
INLINE_GROUPED_CLASSES = YES
INLINE_SIMPLE_STRUCTS = YES
HTML_COLORSTYLE_HUE = 204
HTML_COLORSTYLE_SAT = 120
HTML_COLORSTYLE_GAMMA = 60
HTML_EXTRA_STYLESHEET = "@PROJECT_SOURCE_DIR@/etc/sponge_doxygen.css"
GENERATE_LATEX = NO
EXAMPLE_PATH = "@PROJECT_SOURCE_DIR@/doctests"
# cmake detects whether dot is available
HAVE_DOT = @DOXYGEN_DOT_FOUND@
CLASS_GRAPH = YES
TEMPLATE_RELATIONS = YES
DOT_IMAGE_FORMAT = png
INTERACTIVE_SVG = NO
COLLABORATION_GRAPH = NO
# ??? temporary
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_STATIC = YES
EXTRACT_ANON_NSPACES = YES
# do u liek eclips
GENERATE_ECLIPSEHELP = NO
ECLIPSE_DOC_ID = edu.stanford.cs144.sponge
find_library (LIBPCAP pcap)
find_library (LIBPTHREAD pthread)
macro (add_sponge_exec exec_name)
add_executable ("${exec_name}" "${exec_name}.cc")
target_link_libraries ("${exec_name}" ${ARGN} sponge ${LIBPTHREAD})
endmacro (add_sponge_exec)
set (default_build_type "Release")
if (NOT (CMAKE_BUILD_TYPE_SHADOW STREQUAL CMAKE_BUILD_TYPE))
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message (STATUS "Setting build type to '${default_build_type}'")
set (CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE)
else ()
message (STATUS "Building in ${CMAKE_BUILD_TYPE} mode as requested.")
endif ()
set (CMAKE_BUILD_TYPE_SHADOW ${CMAKE_BUILD_TYPE} CACHE STRING "used to detect changes in build type" FORCE)
endif ()
message (STATUS " NOTE: You can choose a build type by calling cmake with one of:")
message (STATUS " -DCMAKE_BUILD_TYPE=Release -- full optimizations")
message (STATUS " -DCMAKE_BUILD_TYPE=Debug -- better debugging experience in gdb")
message (STATUS " -DCMAKE_BUILD_TYPE=RelASan -- full optimizations plus address and undefined-behavior sanitizers")
message (STATUS " -DCMAKE_BUILD_TYPE=DebugASan -- debug plus sanitizers")
set (CMAKE_CXX_STANDARD 17)
set (CMAKE_EXPORT_COMPILE_COMMANDS ON)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -g -pedantic -pedantic-errors -Werror -Wall -Wextra -Wshadow -Wpointer-arith -Wcast-qual -Wformat=2 -Weffc++ -Wold-style-cast")
# check for supported compiler versions
set (IS_GNU_COMPILER ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU"))
set (IS_CLANG_COMPILER ("${CMAKE_CXX_COMPILER_ID}" MATCHES "[Cc][Ll][Aa][Nn][Gg]"))
set (CXX_VERSION_LT_6 ("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 6))
set (CXX_VERSION_LT_8 ("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 8))
if ((${IS_GNU_COMPILER} AND ${CXX_VERSION_LT_8}) OR (${IS_CLANG_COMPILER} AND ${CXX_VERSION_LT_6}))
message (FATAL_ERROR "You must compile this project with g++ >= 8 or clang >= 6.")
endif ()
if (${IS_CLANG_COMPILER})
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wloop-analysis")
endif ()
# add some flags for the Release, Debug, and DebugSan modes
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb3 -Og")
set (CMAKE_CXX_FLAGS_DEBUGASAN "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=undefined -fsanitize=address")
set (CMAKE_CXX_FLAGS_RELASAN "${CMAKE_CXX_FLAGS_RELEASE} -fsanitize=undefined -fsanitize=address")
if (NOT CLANG_FORMAT)
if (DEFINED ENV{CLANG_FORMAT})
set (CLANG_FORMAT_TMP $ENV{CLANG_FORMAT})
else (NOT DEFINED ENV{CLANG_FORMAT})
set (CLANG_FORMAT_TMP clang-format-6.0)
endif (DEFINED ENV{CLANG_FORMAT})
# figure out which version of clang-format we're using
execute_process (COMMAND ${CLANG_FORMAT_TMP} --version RESULT_VARIABLE CLANG_FORMAT_RESULT OUTPUT_VARIABLE CLANG_FORMAT_VERSION)
if (${CLANG_FORMAT_RESULT} EQUAL 0)
string (REGEX MATCH "version [0-9]" CLANG_FORMAT_VERSION ${CLANG_FORMAT_VERSION})
message (STATUS "Found clang-format " ${CLANG_FORMAT_VERSION})
set(CLANG_FORMAT ${CLANG_FORMAT_TMP} CACHE STRING "clang-format executable name")
endif (${CLANG_FORMAT_RESULT} EQUAL 0)
endif (NOT CLANG_FORMAT)
if (DEFINED CLANG_FORMAT)
file (GLOB_RECURSE ALL_CC_FILES *.cc)
file (GLOB_RECURSE ALL_HH_FILES *.hh)
add_custom_target (format ${CLANG_FORMAT} -i ${ALL_CC_FILES} ${ALL_HH_FILES} COMMENT "Formatted all source files.")
else (NOT DEFINED CLANG_FORMAT)
add_custom_target (format echo "Could not find clang-format. Please install and re-run cmake")
endif (DEFINED CLANG_FORMAT)
if (NOT CLANG_TIDY)
if (DEFINED ENV{CLANG_TIDY})
set (CLANG_TIDY_TMP $ENV{CLANG_TIDY})
else (NOT DEFINED ENV{CLANG_TIDY})
set (CLANG_TIDY_TMP clang-tidy)
endif (DEFINED ENV{CLANG_TIDY})
# is clang-tidy available?
execute_process (COMMAND ${CLANG_TIDY_TMP} --version RESULT_VARIABLE CLANG_TIDY_RESULT OUTPUT_VARIABLE CLANG_TIDY_VERSION)
if (${CLANG_TIDY_RESULT} EQUAL 0)
string (REGEX MATCH "version [0-9]" CLANG_TIDY_VERSION ${CLANG_TIDY_VERSION})
message (STATUS "Found clang-tidy " ${CLANG_TIDY_VERSION})
set (CLANG_TIDY ${CLANG_TIDY_TMP} CACHE STRING "clang-tidy executable name")
endif (${CLANG_TIDY_RESULT} EQUAL 0)
endif (NOT CLANG_TIDY)
if (DEFINED CLANG_TIDY)
file (GLOB_RECURSE ALL_CC_FILES *.cc)
set (CLANG_TIDY_CHECKS "'*,-fuchsia-*,-hicpp-signed-bitwise,-google-build-using-namespace,-android*,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-google-runtime-references,-readability-avoid-const-params-in-decls,-llvm-header-guard'")
foreach (tidy_target ${ALL_CC_FILES})
get_filename_component (basename ${tidy_target} NAME)
get_filename_component (dirname ${tidy_target} DIRECTORY)
get_filename_component (basedir ${dirname} NAME)
set (tidy_target_name "${basedir}__${basename}")
set (tidy_command ${CLANG_TIDY} -checks=${CLANG_TIDY_CHECKS} -header-filter=.* -p=${PROJECT_BINARY_DIR} ${tidy_target})
add_custom_target (tidy_quiet_${tidy_target_name} ${tidy_command} 2>/dev/null)
add_custom_target (tidy_${tidy_target_name} ${tidy_command})
list (APPEND ALL_TIDY_TARGETS tidy_quiet_${tidy_target_name})
list (APPEND ALL_TIDY_VERBOSE_TARGETS tidy_${tidy_target_name})
endforeach (tidy_target)
add_custom_target (tidy DEPENDS ${ALL_TIDY_TARGETS})
add_custom_target (tidy_verbose DEPENDS ${ALL_TIDY_VERBOSE_TARGETS})
endif (DEFINED CLANG_TIDY)
if (NOT CPPCHECK)
if (DEFINED ENV{CPPCHECK})
set (CPPCHECK_TMP $ENV{CPPCHECK})
else (NOT DEFINED ENV{CPPCHECK})
set (CPPCHECK_TMP cppcheck)
endif ()
# is cppcheck available?
execute_process (COMMAND ${CPPCHECK_TMP} --version RESULT_VARIABLE CPPCHECK_RESULT OUTPUT_VARIABLE CPPCHECK_OUTPUT)
if (${CPPCHECK_RESULT} EQUAL 0)
message (STATUS "Found cppcheck")
set (CPPCHECK ${CPPCHECK_TMP} CACHE STRING "cppcheck executable name")
endif()
endif (NOT CPPCHECK)
if (DEFINED CPPCHECK)
add_custom_target (cppcheck ${CPPCHECK} --enable=all --project="${PROJECT_BINARY_DIR}/compile_commands.json")
endif (DEFINED CPPCHECK)
This diff is collapsed.
find_package (Doxygen)
if (DOXYGEN_FOUND)
if (Doxygen_dot_FOUND)
set (DOXYGEN_DOT_FOUND YES)
else (NOT Doxygen_dot_FOUND)
set (DOXYGEN_DOT_FOUND NO)
endif (Doxygen_dot_FOUND)
configure_file ("${PROJECT_SOURCE_DIR}/etc/Doxyfile.in" "${PROJECT_BINARY_DIR}/Doxyfile" @ONLY)
add_custom_target (doc "${DOXYGEN_EXECUTABLE}" "${PROJECT_BINARY_DIR}/Doxyfile"
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
COMMENT "Generate docs using Doxygen" VERBATIM)
endif ()
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<tagfile>
<compound kind="namespace"><name>rfc</name><filename></filename>
<member kind="function">
<type></type>
<name>rfc791</name>
<anchorfile>rfc791</anchorfile>
<anchor></anchor>
<arglist></arglist>
</member>
<member kind="function">
<type></type>
<name>rfc793</name>
<anchorfile>rfc793</anchorfile>
<anchor></anchor>
<arglist></arglist>
</member>
<member kind="function">
<type></type>
<name>rfc6298</name>
<anchorfile>rfc6298</anchorfile>
<anchor></anchor>
<arglist></arglist>
</member>
</compound>
</tagfile>
html, body { background-color: #F8F8F8; }
div.textblock>p,div.memdoc>p,dl.section.note>dd { max-width: 750px; }
div.line,pre.fragment { line-height: 1.5; }
div.contents {
padding: 12px;
margin-top: auto;
margin-bottom: auto;
margin-left: 3%;
margin-right: 6%;
border-radius: 8px;
}
This diff is collapsed.
TUN_IP_PREFIX=169.254
file (GLOB LIB_SOURCES "*.cc" "util/*.cc" "tcp_helpers/*.cc")
add_library (sponge STATIC ${LIB_SOURCES})
#include "byte_stream.hh"
#include <algorithm>
#include <iterator>
#include <stdexcept>
// Dummy implementation of a flow-controlled in-memory byte stream.
// For Lab 0, please replace with a real implementation that passes the
// automated checks run by `make check_lab0`.
// You will need to add private members to the class declaration in `byte_stream.hh`
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
ByteStream::ByteStream(const size_t capacity) { DUMMY_CODE(capacity); }
size_t ByteStream::write(const string &data) {
DUMMY_CODE(data);
return {};
}
//! \param[in] len bytes will be copied from the output side of the buffer
string ByteStream::peek_output(const size_t len) const {
DUMMY_CODE(len);
return {};
}
//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) { DUMMY_CODE(len); }
void ByteStream::end_input() {}
bool ByteStream::input_ended() const { return {}; }
size_t ByteStream::buffer_size() const { return {}; }
bool ByteStream::buffer_empty() const { return {}; }
bool ByteStream::eof() const { return false; }
size_t ByteStream::bytes_written() const { return {}; }
size_t ByteStream::bytes_read() const { return {}; }
size_t ByteStream::remaining_capacity() const { return {}; }
#ifndef SPONGE_LIBSPONGE_BYTE_STREAM_HH
#define SPONGE_LIBSPONGE_BYTE_STREAM_HH
#include <cstddef>
#include <cstdint>
#include <deque>
#include <list>
#include <string>
#include <utility>
//! \brief An in-order byte stream.
//! Bytes are written on the "input" side and read from the "output"
//! side. The byte stream is finite: the writer can end the input,
//! and then no more bytes can be written.
class ByteStream {
private:
// Your code here -- add private members as necessary.
bool _error{}; //!< Flag indicating that the stream suffered an error.
public:
//! Construct a stream with room for `capacity` bytes.
ByteStream(const size_t capacity);
//! \name "Input" interface for the writer
//!@{
//! Write a string of bytes into the stream. Write as many
//! as will fit, and return how many were written.
//! \returns the number of bytes accepted into the stream
size_t write(const std::string &data);
//! \returns the number of additional bytes that the stream has space for
size_t remaining_capacity() const;
//! Signal that the byte stream has reached its ending
void end_input();
//! Indicate that the stream suffered an error.
void set_error() { _error = true; }
//!@}
//! \name "Output" interface for the reader
//!@{
//! Peek at next "len" bytes of the stream
//! \returns a string
std::string peek_output(const size_t len) const;
//! Remove bytes from the buffer
void pop_output(const size_t len);
//! Read (i.e., copy and then pop) the next "len" bytes of the stream
//! \returns a vector of bytes read
std::string read(const size_t len) {
const auto ret = peek_output(len);
pop_output(len);
return ret;
}
//! \returns `true` if the stream input has ended
bool input_ended() const;
//! \returns `true` if the stream has suffered an error
bool error() const { return _error; }
//! \returns the maximum amount that can currently be read from the stream
size_t buffer_size() const;
//! \returns `true` if the buffer is empty
bool buffer_empty() const;
//! \returns `true` if the output has reached the ending
bool eof() const;
//!@}
//! \name General accounting
//!@{
//! Total number of bytes written
size_t bytes_written() const;
//! Total number of bytes popped
size_t bytes_read() const;
//!@}
};
#endif // SPONGE_LIBSPONGE_BYTE_STREAM_HH
#include "stream_reassembler.hh"
// Dummy implementation of a stream reassembler.
// For Lab 1, please replace with a real implementation that passes the
// automated checks run by `make check_lab1`.
// You will need to add private members to the class declaration in `stream_reassembler.hh`
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
StreamReassembler::StreamReassembler(const size_t capacity) : _output(capacity), _capacity(capacity) {}
//! \details This function accepts a substring (aka a segment) of bytes,
//! possibly out-of-order, from the logical stream, and assembles any newly
//! contiguous substrings and writes them into the output stream in order.
void StreamReassembler::push_substring(const string &data, const size_t index, const bool eof) {
DUMMY_CODE(data, index, eof);
}
size_t StreamReassembler::unassembled_bytes() const { return {}; }
bool StreamReassembler::empty() const { return {}; }
#ifndef SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH
#define SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH
#include "byte_stream.hh"
#include <cstdint>
#include <string>
//! \brief A class that assembles a series of excerpts from a byte stream (possibly out of order,
//! possibly overlapping) into an in-order byte stream.
class StreamReassembler {
private:
// Your code here -- add private members as necessary.
ByteStream _output; //!< The reassembled in-order byte stream
size_t _capacity; //!< The maximum number of bytes
public:
//! \brief Construct a `StreamReassembler` that will store up to `capacity` bytes.
//! \note This capacity limits both the bytes that have been reassembled,
//! and those that have not yet been reassembled.
StreamReassembler(const size_t capacity);
//! \brief Receives a substring and writes any newly contiguous bytes into the stream.
//!
//! If accepting all the data would overflow the `capacity` of this
//! `StreamReassembler`, then only the part of the data that fits will be
//! accepted. If the substring is only partially accepted, then the `eof`
//! will be disregarded.
//!
//! \param data the string being added
//! \param index the index of the first byte in `data`
//! \param eof whether or not this segment ends with the end of the stream
void push_substring(const std::string &data, const uint64_t index, const bool eof);
//! \name Access the reassembled byte stream
//!@{
const ByteStream &stream_out() const { return _output; }
ByteStream &stream_out() { return _output; }
//!@}
//! The number of bytes in the substrings stored but not yet reassembled
//!
//! \note If the byte at a particular index has been submitted twice, it
//! should only be counted once for the purpose of this function.
size_t unassembled_bytes() const;
//! \brief Is the internal state empty (other than the output stream)?
//! \returns `true` if no substrings are waiting to be assembled
bool empty() const;
};
#endif // SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH
#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;
}
}
#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
#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 = 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();
UDPSocket::sendto(config().destination, seg.serialize(0));
}
//! Specialize LossyFdAdapter to TCPOverUDPSocketAdapter
template class LossyFdAdapter<TCPOverUDPSocketAdapter>;
#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; }
};
//! \brief A FD adaptor that reads and writes TCP segments in UDP payloads
class TCPOverUDPSocketAdapter : public FdAdapterBase, public UDPSocket {
public:
//! Construct from a UDPSocket sliced into a FileDescriptor
explicit TCPOverUDPSocketAdapter(FileDescriptor &&fd) : UDPSocket(std::move(fd)) {}
//! 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);
};
//! Typedef for TCPOverUDPSocketAdapter
using LossyTCPOverUDPSocketAdapter = LossyFdAdapter<TCPOverUDPSocketAdapter>;
#endif // SPONGE_LIBSPONGE_FD_ADAPTER_HH
#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;
}
#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
#include "ipv4_header.hh"
#include "util.hh"
#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();
}
#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;
};
//! \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
#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(FileDescriptor &&fd) : _adapter(std::move(fd)) {}
//! \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
//!@}
};
#endif // SPONGE_LIBSPONGE_LOSSY_FD_ADAPTER_HH
#ifndef SPONGE_LIBSPONGE_TCP_CONFIG_HH
#define SPONGE_LIBSPONGE_TCP_CONFIG_HH
#include "address.hh"
#include "wrapping_integers.hh"
#include <cstddef>
#include <cstdint>
#include <optional>
//! Config for TCP sender and receiver
class TCPConfig {
public:
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 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
uint16_t rt_timeout = TIMEOUT_DFLT; //!< Initial value of the retransmission timeout, in milliseconds
size_t recv_capacity = DEFAULT_CAPACITY; //!< Receive capacity, in bytes
size_t send_capacity = DEFAULT_CAPACITY; //!< Sender capacity, in bytes
std::optional<WrappingInt32> fixed_isn{};
};
//! Config for classes derived from FdAdapter
class FdAdapterConfig {
public:
Address source{"0", 0}; //!< Source address and port
Address destination{"0", 0}; //!< Destination address and port
uint16_t loss_rate_dn = 0; //!< Downlink loss rate (for LossyFdAdapter)
uint16_t loss_rate_up = 0; //!< Uplink loss rate (for LossyFdAdapter)
};
#endif // SPONGE_LIBSPONGE_TCP_CONFIG_HH
#include "tcp_header.hh"
#include <sstream>
using namespace std;
//! \param[in,out] p is a NetParser from which the TCP 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 inside the NetParser is too short to contain a header
//! - the header's `doff` field is shorter than the minimum allowed
//! - there is less data in the header than the `doff` field claims
//! - the checksum is bad
ParseResult TCPHeader::parse(NetParser &p) {
sport = p.u16(); // source port
dport = p.u16(); // destination port
seqno = WrappingInt32{p.u32()}; // sequence number
ackno = WrappingInt32{p.u32()}; // ack number
doff = p.u8() >> 4; // data offset
const uint8_t fl_b = p.u8(); // byte including flags
urg = static_cast<bool>(fl_b & 0b0010'0000); // binary literals and ' digit separator since C++14!!!
ack = static_cast<bool>(fl_b & 0b0001'0000);
psh = static_cast<bool>(fl_b & 0b0000'1000);
rst = static_cast<bool>(fl_b & 0b0000'0100);
syn = static_cast<bool>(fl_b & 0b0000'0010);
fin = static_cast<bool>(fl_b & 0b0000'0001);
win = p.u16(); // window size
cksum = p.u16(); // checksum
uptr = p.u16(); // urgent pointer
if (doff < 5) {
return ParseResult::HeaderTooShort;
}
// skip any options or anything extra in the header
p.remove_prefix(doff * 4 - TCPHeader::LENGTH);
if (p.error()) {
return p.get_error();
}
return ParseResult::NoError;
}
//! Serialize the TCPHeader to a string (does not recompute the checksum)
string TCPHeader::serialize() const {
// sanity check
if (doff < 5) {
throw runtime_error("TCP header too short");
}
string ret;
ret.reserve(4 * doff);
NetUnparser::u16(ret, sport); // source port
NetUnparser::u16(ret, dport); // destination port
NetUnparser::u32(ret, seqno.raw_value()); // sequence number
NetUnparser::u32(ret, ackno.raw_value()); // ack number
NetUnparser::u8(ret, doff << 4); // data offset
const uint8_t fl_b = (urg ? 0b0010'0000 : 0) | (ack ? 0b0001'0000 : 0) | (psh ? 0b0000'1000 : 0) |
(rst ? 0b0000'0100 : 0) | (syn ? 0b0000'0010 : 0) | (fin ? 0b0000'0001 : 0);
NetUnparser::u8(ret, fl_b); // flags
NetUnparser::u16(ret, win); // window size
NetUnparser::u16(ret, cksum); // checksum
NetUnparser::u16(ret, uptr); // urgent pointer
ret.resize(4 * doff); // expand header to advertised size
return ret;
}
//! \returns A string with the header's contents
string TCPHeader::to_string() const {
stringstream ss{};
ss << hex << boolalpha << "TCP source port: " << +sport << '\n'
<< "TCP dest port: " << +dport << '\n'
<< "TCP seqno: " << seqno << '\n'
<< "TCP ackno: " << ackno << '\n'
<< "TCP doff: " << +doff << '\n'
<< "Flags: urg: " << urg << " ack: " << ack << " psh: " << psh << " rst: " << rst << " syn: " << syn
<< " fin: " << fin << '\n'
<< "TCP winsize: " << +win << '\n'
<< "TCP cksum: " << +cksum << '\n'
<< "TCP uptr: " << +uptr << '\n';
return ss.str();
}
string TCPHeader::summary() const {
stringstream ss{};
ss << "Header(flags=" << (syn ? "S" : "") << (ack ? "A" : "") << (rst ? "R" : "") << (fin ? "F" : "")
<< ",seqno=" << seqno << ",ack=" << ackno << ",win=" << win << ")";
return ss.str();
}
bool TCPHeader::operator==(const TCPHeader &other) const {
// TODO(aozdemir) more complete check (right now we omit cksum, src, dst
return seqno == other.seqno && ackno == other.ackno && doff == other.doff && urg == other.urg && ack == other.ack &&
psh == other.psh && rst == other.rst && syn == other.syn && fin == other.fin && win == other.win &&
uptr == other.uptr;
}
#ifndef SPONGE_LIBSPONGE_TCP_HEADER_HH
#define SPONGE_LIBSPONGE_TCP_HEADER_HH
#include "parser.hh"
#include "wrapping_integers.hh"
//! \brief [TCP](\ref rfc::rfc793) segment header
//! \note TCP options are not supported
struct TCPHeader {
static constexpr size_t LENGTH = 20; //!< [TCP](\ref rfc::rfc793) header length, not including options
//! \struct TCPHeader
//! ~~~{.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
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Source Port | Destination Port |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Sequence Number |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Acknowledgment Number |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Data | |U|A|P|R|S|F| |
//! | Offset| Reserved |R|C|S|S|Y|I| Window |
//! | | |G|K|H|T|N|N| |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Checksum | Urgent Pointer |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Options | Padding |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | data |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! ~~~
//! \name TCP Header fields
//!@{
uint16_t sport = 0; //!< source port
uint16_t dport = 0; //!< destination port
WrappingInt32 seqno{0}; //!< sequence number
WrappingInt32 ackno{0}; //!< ack number
uint8_t doff = LENGTH / 4; //!< data offset
bool urg = false; //!< urgent flag
bool ack = false; //!< ack flag
bool psh = false; //!< push flag
bool rst = false; //!< rst flag
bool syn = false; //!< syn flag
bool fin = false; //!< fin flag
uint16_t win = 0; //!< window size
uint16_t cksum = 0; //!< checksum
uint16_t uptr = 0; //!< urgent pointer
//!@}
//! Parse the TCP fields from the provided NetParser
ParseResult parse(NetParser &p);
//! Serialize the TCP fields
std::string serialize() 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;
bool operator==(const TCPHeader &other) const;
};
#endif // SPONGE_LIBSPONGE_TCP_HEADER_HH
#include "tcp_segment.hh"
#include "parser.hh"
#include "util.hh"
#include <variant>
using namespace std;
//! \param[in] buffer string/Buffer to be parsed
//! \param[in] datagram_layer_checksum pseudo-checksum from the lower-layer protocol
ParseResult TCPSegment::parse(const Buffer buffer, const uint32_t datagram_layer_checksum) {
InternetChecksum check(datagram_layer_checksum);
check.add(buffer);
if (check.value()) {
return ParseResult::BadChecksum;
}
NetParser p{buffer};
_header.parse(p);
_payload = p.buffer();
return p.get_error();
}
size_t TCPSegment::length_in_sequence_space() const {
return payload().str().size() + (header().syn ? 1 : 0) + (header().fin ? 1 : 0);
}
//! \param[in] datagram_layer_checksum pseudo-checksum from the lower-layer protocol
BufferList TCPSegment::serialize(const uint32_t datagram_layer_checksum) const {
TCPHeader header_out = _header;
header_out.cksum = 0;
// calculate checksum -- taken over entire segment
InternetChecksum check(datagram_layer_checksum);
check.add(header_out.serialize());
check.add(_payload);
header_out.cksum = check.value();
BufferList ret;
ret.append(header_out.serialize());
ret.append(_payload);
return ret;
}
#ifndef SPONGE_LIBSPONGE_TCP_SEGMENT_HH
#define SPONGE_LIBSPONGE_TCP_SEGMENT_HH
#include "buffer.hh"
#include "tcp_header.hh"
#include <cstdint>
//! \brief [TCP](\ref rfc::rfc793) segment
class TCPSegment {
private:
TCPHeader _header{};
Buffer _payload{};
public:
//! \brief Parse the segment from a string
ParseResult parse(const Buffer buffer, const uint32_t datagram_layer_checksum = 0);
//! \brief Serialize the segment to a string
BufferList serialize(const uint32_t datagram_layer_checksum = 0) const;
//! \name Accessors
//!@{
const TCPHeader &header() const { return _header; }
TCPHeader &header() { return _header; }
const Buffer &payload() const { return _payload; }
Buffer &payload() { return _payload; }
//!@}
//! \brief Segment's length in sequence space
//! \note Equal to payload length plus one byte if SYN is set, plus one byte if FIN is set
size_t length_in_sequence_space() const;
};
#endif // SPONGE_LIBSPONGE_TCP_SEGMENT_HH
This diff is collapsed.
#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 "tunfd_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, FileDescriptor &&dgramfd);
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 FileDescriptor that the TCPConnection thread will use to read and write datagrams
explicit TCPSpongeSocket(FileDescriptor &&dgramfd);
//! 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
#include "tcp_state.hh"
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) {
if (receiver.stream_out().error()) {
return TCPReceiverStateSummary::ERROR;
} else if (not receiver.ackno().has_value()) {
return TCPReceiverStateSummary::LISTEN;
} else if (receiver.stream_out().input_ended()) {
return TCPReceiverStateSummary::FIN_RECV;
} else {
return TCPReceiverStateSummary::SYN_RECV;
}
}
string TCPState::state_summary(const TCPSender &sender) {
if (sender.stream_in().error()) {
return TCPSenderStateSummary::ERROR;
} else if (sender.next_seqno_absolute() == 0) {
return TCPSenderStateSummary::CLOSED;
} else if (sender.next_seqno_absolute() == sender.bytes_in_flight()) {
return TCPSenderStateSummary::SYN_SENT;
} else if (not sender.stream_in().eof()) {
return TCPSenderStateSummary::SYN_ACKED;
} else if (sender.next_seqno_absolute() < sender.stream_in().bytes_written() + 2) {
return TCPSenderStateSummary::SYN_ACKED;
} else if (sender.bytes_in_flight()) {
return TCPSenderStateSummary::FIN_SENT;
} else {
return TCPSenderStateSummary::FIN_ACKED;
}
}
#ifndef SPONGE_LIBSPONGE_TCP_STATE
#define SPONGE_LIBSPONGE_TCP_STATE
#include "tcp_receiver.hh"
#include "tcp_sender.hh"
#include <string>
//! \brief Summary of a TCPConnection's internal state
//!
//! Most TCP implementations have a global per-connection state
//! machine, as described in the [TCP](\ref rfc::rfc793)
//! specification. Sponge is a bit different: we have factored the
//! connection into two independent parts (the sender and the
//! receiver). The TCPSender and TCPReceiver maintain their interval
//! state variables independently (e.g. next_seqno, number of bytes in
//! flight, or whether each stream has ended). There is no notion of a
//! discrete state machine or much overarching state outside the
//! sender and receiver. To test that Sponge follows the TCP spec, we
//! use this class to compare the "official" states with Sponge's
//! sender/receiver states and two variables that belong to the
//! overarching TCPConnection object.
class TCPState {
private:
std::string _sender{};
std::string _receiver{};
bool _active{true};
bool _linger_after_streams_finish{true};
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
static std::string state_summary(const TCPReceiver &receiver);
//! \brief Summarize the state of a TCPSender in a string
static std::string state_summary(const TCPSender &receiver);
};
namespace TCPReceiverStateSummary {
const std::string ERROR = "error (connection was reset)";
const std::string LISTEN = "waiting for stream to begin (listening for SYN)";
const std::string SYN_RECV = "stream started";
const std::string FIN_RECV = "stream finished";
} // namespace TCPReceiverStateSummary
namespace TCPSenderStateSummary {
const std::string ERROR = "error (connection was reset)";
const std::string CLOSED = "waiting for stream to begin (no SYN sent)";
const std::string SYN_SENT = "stream started but nothing acknowledged";
const std::string SYN_ACKED = "stream ongoing";
const std::string FIN_SENT = "stream finished (FIN sent) but not fully acknowledged";
const std::string FIN_ACKED = "stream finished and fully acknowledged";
} // namespace TCPSenderStateSummary
#endif // SPONGE_LIBSPONGE_TCP_STATE
#include "tunfd_adapter.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 first attempts to parse an IP header from the next
//! payload read from the TUN device, then 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 FSM 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> TCPOverIPv4OverTunFdAdapter::read() {
// is the packet a valid IPv4 datagram?
auto ip_dgram = IPv4Datagram{};
if (ParseResult::NoError != ip_dgram.parse(FileDescriptor::read())) {
return {};
}
// 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;
}
//! Serializes a TCP segment into an IPv4 datagram, serialize the IPv4 datagram, and send it to the TUN device.
//! \param[in] seg is the TCP segment to write
void TCPOverIPv4OverTunFdAdapter::write(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
IPv4Datagram 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());
// send
FileDescriptor::write(ip_dgram.serialize());
}
//! Specialize LossyFdAdapter to TCPOverIPv4OverTunFdAdapter
template class LossyFdAdapter<TCPOverIPv4OverTunFdAdapter>;
#ifndef SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH
#define SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH
#include "fd_adapter.hh"
#include "file_descriptor.hh"
#include "lossy_fd_adapter.hh"
#include "tcp_segment.hh"
#include <optional>
#include <utility>
//! \brief A FD adapter for IPv4 datagrams read from and written to a TUN device
class TCPOverIPv4OverTunFdAdapter : public FdAdapterBase, public FileDescriptor {
public:
//! Construct from a TunFD sliced into a FileDescriptor
explicit TCPOverIPv4OverTunFdAdapter(FileDescriptor &&fd) : FileDescriptor(std::move(fd)) {}
//! Attempts to read and parse an IPv4 datagram containing a TCP segment related to the current connection
std::optional<TCPSegment> read();
//! Creates an IPv4 datagram from a TCP segment and writes it to the TUN device
void write(TCPSegment &seg);
};
//! Typedef for TCPOverIPv4OverTunFdAdapter
using LossyTCPOverIPv4OverTunFdAdapter = LossyFdAdapter<TCPOverIPv4OverTunFdAdapter>;
#endif // SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH
#include "tcp_receiver.hh"
// Dummy implementation of a TCP receiver
// For Lab 2, please replace with a real implementation that passes the
// automated checks run by `make check_lab2`.
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
bool TCPReceiver::segment_received(const TCPSegment &seg) {
DUMMY_CODE(seg);
return {};
}
optional<WrappingInt32> TCPReceiver::ackno() const { return {}; }
size_t TCPReceiver::window_size() const { return {}; }
#ifndef SPONGE_LIBSPONGE_TCP_RECEIVER_HH
#define SPONGE_LIBSPONGE_TCP_RECEIVER_HH
#include "byte_stream.hh"
#include "stream_reassembler.hh"
#include "tcp_segment.hh"
#include "wrapping_integers.hh"
#include <optional>
//! \brief The "receiver" part of a TCP implementation.
//! Receives and reassembles segments into a ByteStream, and computes
//! the acknowledgment number and window size to advertise back to the
//! remote TCPSender.
class TCPReceiver {
//! Our data structure for re-assembling bytes.
StreamReassembler _reassembler;
//! The maximum number of bytes we'll store.
size_t _capacity;
public:
//! \brief Construct a TCP receiver
//!
//! \param capacity the maximum number of bytes that the receiver will
//! store in its buffers at any give time.
TCPReceiver(const size_t capacity) : _reassembler(capacity), _capacity(capacity) {}
//! \name Accessors to provide feedback to the remote TCPSender
//!@{
//! \brief The ackno that should be sent to the peer
//! \returns empty if no SYN has been received
//!
//! This is the beginning of the receiver's window, or in other words, the sequence number
//! of the first byte in the stream that the receiver hasn't received.
std::optional<WrappingInt32> ackno() const;
//! \brief The window size that should be sent to the peer
//!
//! Operationally: the capacity minus the number of bytes that the
//! TCPReceiver is holding in its byte stream (those that have been
//! reassembled, but not consumed).
//!
//! Formally: the difference between (a) the sequence number of
//! the first byte that falls after the window (and will not be
//! accepted by the receiver) and (b) the sequence number of the
//! beginning of the window (the ackno).
size_t window_size() const;
//!@}
//! \brief number of bytes stored but not yet reassembled
size_t unassembled_bytes() const { return _reassembler.unassembled_bytes(); }
//! \brief handle an inbound segment
//! \returns `true` if any part of the segment was inside the window
bool segment_received(const TCPSegment &seg);
//! \name "Output" interface for the reader
//!@{
ByteStream &stream_out() { return _reassembler.stream_out(); }
const ByteStream &stream_out() const { return _reassembler.stream_out(); }
//!@}
};
#endif // SPONGE_LIBSPONGE_TCP_RECEIVER_HH
#include "tcp_sender.hh"
#include "tcp_config.hh"
#include <random>
// Dummy implementation of a TCP sender
// For Lab 3, please replace with a real implementation that passes the
// automated checks run by `make check_lab3`.
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
//! \param[in] capacity the capacity of the outgoing byte stream
//! \param[in] retx_timeout the initial amount of time to wait before retransmitting the oldest outstanding segment
//! \param[in] fixed_isn the Initial Sequence Number to use, if set (otherwise uses a random ISN)
TCPSender::TCPSender(const size_t capacity, const uint16_t retx_timeout, const std::optional<WrappingInt32> fixed_isn)
: _isn(fixed_isn.value_or(WrappingInt32{random_device()()}))
, _initial_retransmission_timeout{retx_timeout}
, _stream(capacity) {}
uint64_t TCPSender::bytes_in_flight() const { return {}; }
void TCPSender::fill_window() {}
//! \param ackno The remote receiver's ackno (acknowledgment number)
//! \param window_size The remote receiver's advertised window size
//! \returns `false` if the ackno appears invalid (acknowledges something the TCPSender hasn't sent yet)
bool TCPSender::ack_received(const WrappingInt32 ackno, const uint16_t window_size) {
DUMMY_CODE(ackno, window_size);
return {};
}
//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method
void TCPSender::tick(const size_t ms_since_last_tick) { DUMMY_CODE(ms_since_last_tick); }
unsigned int TCPSender::consecutive_retransmissions() const { return {}; }
void TCPSender::send_empty_segment() {}
#ifndef SPONGE_LIBSPONGE_TCP_SENDER_HH
#define SPONGE_LIBSPONGE_TCP_SENDER_HH
#include "byte_stream.hh"
#include "tcp_config.hh"
#include "tcp_segment.hh"
#include "wrapping_integers.hh"
#include <functional>
#include <queue>
//! \brief The "sender" part of a TCP implementation.
//! Accepts a ByteStream, divides it up into segments and sends the
//! segments, keeps track of which segments are still in-flight,
//! maintains the Retransmission Timer, and retransmits in-flight
//! segments if the retransmission timer expires.
class TCPSender {
private:
//! our initial sequence number, the number for our SYN.
WrappingInt32 _isn;
//! outbound queue of segments that the TCPSender wants sent
std::queue<TCPSegment> _segments_out{};
//! retransmission timer for the connection
unsigned int _initial_retransmission_timeout;
//! outgoing stream of bytes that have not yet been sent
ByteStream _stream;
//! the (absolute) sequence number for the next byte to be sent
uint64_t _next_seqno{0};
public:
//! Initialize a TCPSender
TCPSender(const size_t capacity = TCPConfig::DEFAULT_CAPACITY,
const uint16_t retx_timeout = TCPConfig::TIMEOUT_DFLT,
const std::optional<WrappingInt32> fixed_isn = {});
//! \name "Input" interface for the writer
//!@{
ByteStream &stream_in() { return _stream; }
const ByteStream &stream_in() const { return _stream; }
//!@}
//! \name Methods that can cause the TCPSender to send a segment
//!@{
//! \brief A new acknowledgment was received
bool ack_received(const WrappingInt32 ackno, const uint16_t window_size);
//! \brief Generate an empty-payload segment (useful for creating empty ACK segments)
void send_empty_segment();
//! \brief create and send segments to fill as much of the window as possible
void fill_window();
//! \brief Notifies the TCPSender of the passage of time
void tick(const size_t ms_since_last_tick);
//!@}
//! \name Accessors
//!@{
//! \brief How many sequence numbers are occupied by segments sent but not yet acknowledged?
//! \note count is in "sequence space," i.e. SYN and FIN each count for one byte
//! (see TCPSegment::length_in_sequence_space())
size_t bytes_in_flight() const;
//! \brief Number of consecutive retransmissions that have occurred in a row
unsigned int consecutive_retransmissions() const;
//! \brief TCPSegments that the TCPSender has enqueued for transmission.
//! \note These must be dequeued and sent by the TCPConnection,
//! which will need to fill in the fields that are set by the TCPReceiver
//! (ackno and window size) before sending.
std::queue<TCPSegment> &segments_out() { return _segments_out; }
//!@}
//! \name What is the next sequence number? (used for testing)
//!@{
//! \brief absolute seqno for the next byte to be sent
uint64_t next_seqno_absolute() const { return _next_seqno; }
//! \brief relative seqno for the next byte to be sent
WrappingInt32 next_seqno() const { return wrap(_next_seqno, _isn); }
//!@}
};
#endif // SPONGE_LIBSPONGE_TCP_SENDER_HH
#include "address.hh"
#include "util.hh"
#include <arpa/inet.h>
#include <cstring>
#include <memory>
#include <netdb.h>
#include <stdexcept>
#include <system_error>
using namespace std;
//! Converts Raw to `sockaddr *`.
Address::Raw::operator sockaddr *() { return reinterpret_cast<sockaddr *>(&storage); }
//! Converts Raw to `const sockaddr *`.
Address::Raw::operator const sockaddr *() const { return reinterpret_cast<const sockaddr *>(&storage); }
//! \param[in] addr points to a raw socket address
//! \param[in] size is `addr`'s length
Address::Address(const sockaddr *addr, const size_t size) : _size(size) {
// make sure proposed sockaddr can fit
if (size > sizeof(_address.storage)) {
throw runtime_error("invalid sockaddr size");
}
memcpy(&_address.storage, addr, size);
}
//! Error category for getaddrinfo and getnameinfo failures.
class gai_error_category : public error_category {
public:
//! The name of the wrapped error
const char *name() const noexcept override { return "gai_error_category"; }
//! \brief An error message
//! \param[in] return_value the error return value from [getaddrinfo(3)](\ref man3::getaddrinfo)
//! or [getnameinfo(3)](\ref man3::getnameinfo)
string message(const int return_value) const noexcept override { return gai_strerror(return_value); }
};
//! \param[in] node is the hostname or dotted-quad address
//! \param[in] service is the service name or numeric string
//! \param[in] hints are criteria for resolving the supplied name
Address::Address(const string &node, const string &service, const addrinfo &hints) : _size() {
// prepare for the answer
addrinfo *resolved_address = nullptr;
// look up the name or names
const int gai_ret = getaddrinfo(node.c_str(), service.c_str(), &hints, &resolved_address);
if (gai_ret != 0) {
throw tagged_error(gai_error_category(), "getaddrinfo(" + node + ", " + service + ")", gai_ret);
}
// if success, should always have at least one entry
if (resolved_address == nullptr) {
throw runtime_error("getaddrinfo returned successfully but with no results");
}
// put resolved_address in a wrapper so it will get freed if we have to throw an exception
auto addrinfo_deleter = [](addrinfo *const x) { freeaddrinfo(x); };
unique_ptr<addrinfo, decltype(addrinfo_deleter)> wrapped_address(resolved_address, move(addrinfo_deleter));
// assign to our private members (making sure size fits)
*this = Address(wrapped_address->ai_addr, wrapped_address->ai_addrlen);
}
//! \brief Build a `struct addrinfo` containing hints for [getaddrinfo(3)](\ref man3::getaddrinfo)
//! \param[in] ai_flags is the value of the `ai_flags` field in the [struct addrinfo](\ref man3::getaddrinfo)
//! \param[in] ai_family is the value of the `ai_family` field in the [struct addrinfo](\ref man3::getaddrinfo)
static inline addrinfo make_hints(const int ai_flags, const int ai_family) {
addrinfo hints{}; // value initialized to all zeros
hints.ai_flags = ai_flags;
hints.ai_family = ai_family;
return hints;
}
//! \param[in] hostname to resolve
//! \param[in] service name (from `/etc/services`, e.g., "http" is port 80)
Address::Address(const string &hostname, const string &service)
: Address(hostname, service, make_hints(AI_ALL, AF_INET)) {}
//! \param[in] ip address as a dotted quad ("1.1.1.1")
//! \param[in] port number
Address::Address(const string &ip, const uint16_t port)
// tell getaddrinfo that we don't want to resolve anything
: Address(ip, ::to_string(port), make_hints(AI_NUMERICHOST | AI_NUMERICSERV, AF_INET)) {}
// accessors
pair<string, uint16_t> Address::ip_port() const {
array<char, NI_MAXHOST> ip{};
array<char, NI_MAXSERV> port{};
const int gni_ret =
getnameinfo(_address, _size, ip.data(), ip.size(), port.data(), port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
if (gni_ret != 0) {
throw tagged_error(gai_error_category(), "getnameinfo", gni_ret);
}
return {ip.data(), stoi(port.data())};
}
string Address::to_string() const {
const auto ip_and_port = ip_port();
return ip_and_port.first + ":" + ::to_string(ip_and_port.second);
}
uint32_t Address::ipv4_numeric() const {
if (_address.storage.ss_family != AF_INET or _size != sizeof(sockaddr_in)) {
throw runtime_error("ipv4_numeric called on non-IPV4 address");
}
sockaddr_in ipv4_addr{};
memcpy(&ipv4_addr, &_address.storage, _size);
return be32toh(ipv4_addr.sin_addr.s_addr);
}
// equality
bool Address::operator==(const Address &other) const {
if (_size != other._size) {
return false;
}
return 0 == memcmp(&_address, &other._address, _size);
}
#ifndef SPONGE_LIBSPONGE_ADDRESS_HH
#define SPONGE_LIBSPONGE_ADDRESS_HH
#include <cstddef>
#include <cstdint>
#include <netdb.h>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <utility>
//! Wrapper around [IPv4 addresses](@ref man7::ip) and DNS operations.
class Address {
public:
//! \brief Wrapper around [sockaddr_storage](@ref man7::socket).
//! \details A `sockaddr_storage` is enough space to store any socket address (IPv4 or IPv6).
class Raw {
public:
sockaddr_storage storage{}; //!< The wrapped struct itself.
operator sockaddr *();
operator const sockaddr *() const;
};
private:
socklen_t _size; //!< Size of the wrapped address.
Raw _address{}; //!< A wrapped [sockaddr_storage](@ref man7::socket) containing the address.
//! Constructor from ip/host, service/port, and hints to the resolver.
Address(const std::string &node, const std::string &service, const addrinfo &hints);
public:
//! Construct by resolving a hostname and servicename.
Address(const std::string &hostname, const std::string &service);
//! Construct from dotted-quad string ("18.243.0.1") and numeric port.
Address(const std::string &ip, const std::uint16_t port);
//! Construct from a [sockaddr *](@ref man7::socket).
Address(const sockaddr *addr, const std::size_t size);
//! Equality comparison.
bool operator==(const Address &other) const;
bool operator!=(const Address &other) const { return not operator==(other); }
//! \name Conversions
//!@{
//! Dotted-quad IP address string ("18.243.0.1") and numeric port.
std::pair<std::string, uint16_t> ip_port() const;
//! Dotted-quad IP address string ("18.243.0.1").
std::string ip() const { return ip_port().first; }
//! Numeric port (host byte order).
uint16_t port() const { return ip_port().second; }
//! Numeric IP address as an integer (i.e., in [host byte order](\ref man3::byteorder)).
uint32_t ipv4_numeric() const;
//! Human-readable string, e.g., "8.8.8.8:53".
std::string to_string() const;
//!@}
//! \name Low-level operations
//!@{
//! Size of the underlying address storage.
socklen_t size() const { return _size; }
//! Const pointer to the underlying socket address storage.
operator const sockaddr *() const { return _address; }
//!@}
};
//! \class Address
//! For example, you can do DNS lookups:
//!
//! \include address_example_1.cc
//!
//! or you can specify an IP address and port number:
//!
//! \include address_example_2.cc
//!
//! Once you have an address, you can convert it to other useful representations, e.g.,
//!
//! \include address_example_3.cc
#endif // SPONGE_LIBSPONGE_ADDRESS_HH
#include "buffer.hh"
using namespace std;
void Buffer::remove_prefix(const size_t n) {
if (n > str().size()) {
throw out_of_range("Buffer::remove_prefix");
}
_starting_offset += n;
if (_storage and _starting_offset == _storage->size()) {
_storage.reset();
}
}
void BufferList::append(const BufferList &other) {
for (const auto &buf : other._buffers) {
_buffers.push_back(buf);
}
}
BufferList::operator Buffer() const {
switch (_buffers.size()) {
case 0:
return {};
case 1:
return _buffers[0];
default: {
throw runtime_error(
"BufferList: please use concatenate() to combine a multi-Buffer BufferList into one Buffer");
}
}
}
string BufferList::concatenate() const {
std::string ret;
ret.reserve(size());
for (const auto &buf : _buffers) {
ret.append(buf);
}
return ret;
}
size_t BufferList::size() const {
size_t ret = 0;
for (const auto &buf : _buffers) {
ret += buf.size();
}
return ret;
}
void BufferList::remove_prefix(size_t n) {
while (n > 0) {
if (_buffers.empty()) {
throw std::out_of_range("BufferList::remove_prefix");
}
if (n < _buffers.front().str().size()) {
_buffers.front().remove_prefix(n);
n = 0;
} else {
n -= _buffers.front().str().size();
_buffers.pop_front();
}
}
}
BufferViewList::BufferViewList(const BufferList &buffers) {
for (const auto &x : buffers.buffers()) {
_views.push_back(x);
}
}
void BufferViewList::remove_prefix(size_t n) {
while (n > 0) {
if (_views.empty()) {
throw std::out_of_range("BufferListView::remove_prefix");
}
if (n < _views.front().size()) {
_views.front().remove_prefix(n);
n = 0;
} else {
n -= _views.front().size();
_views.pop_front();
}
}
}
size_t BufferViewList::size() const {
size_t ret = 0;
for (const auto &buf : _views) {
ret += buf.size();
}
return ret;
}
vector<iovec> BufferViewList::as_iovecs() const {
vector<iovec> ret;
ret.reserve(_views.size());
for (const auto &x : _views) {
ret.push_back({const_cast<char *>(x.data()), x.size()});
}
return ret;
}
#ifndef SPONGE_LIBSPONGE_BUFFER_HH
#define SPONGE_LIBSPONGE_BUFFER_HH
#include <algorithm>
#include <deque>
#include <memory>
#include <numeric>
#include <string>
#include <string_view>
#include <sys/uio.h>
#include <vector>
//! \brief A reference-counted read-only string that can discard bytes from the front
class Buffer {
private:
std::shared_ptr<std::string> _storage{};
size_t _starting_offset{};
public:
Buffer() = default;
//! \brief Construct by taking ownership of a string
Buffer(std::string &&str) noexcept : _storage(std::make_shared<std::string>(std::move(str))) {}
//! \name Expose contents as a std::string_view
//!@{
std::string_view str() const {
if (not _storage) {
return {};
}
return {_storage->data() + _starting_offset, _storage->size() - _starting_offset};
}
operator std::string_view() const { return str(); }
//!@}
//! \brief Get character at location `n`
uint8_t at(const size_t n) const { return str().at(n); }
//! \brief Size of the string
size_t size() const { return str().size(); }
//! \brief Make a copy to a new std::string
std::string copy() const { return std::string(str()); }
//! \brief Discard the first `n` bytes of the string (does not require a copy or move)
//! \note Doesn't free any memory until the whole string has been discarded in all copies of the Buffer.
void remove_prefix(const size_t n);
};
//! \brief A reference-counted discontiguous string that can discard bytes from the front
//! \note Used to model packets that contain multiple sets of headers
//! + a payload. This allows us to prepend headers (e.g., to
//! encapsulate a TCP payload in a TCPSegment, and then encapsulate
//! the TCPSegment in an IPv4Datagram) without copying the payload.
class BufferList {
private:
std::deque<Buffer> _buffers{};
public:
//! \name Constructors
//!@{
BufferList() = default;
//! \brief Construct from a Buffer
BufferList(Buffer buffer) : _buffers{buffer} {}
//! \brief Construct by taking ownership of a std::string
BufferList(std::string &&str) noexcept {
Buffer buf{std::move(str)};
append(buf);
}
//!@}
//! \brief Access the underlying queue of Buffers
const std::deque<Buffer> &buffers() const { return _buffers; }
//! \brief Append a BufferList
void append(const BufferList &other);
//! \brief Transform to a Buffer
//! \note Throws an exception unless BufferList is contiguous
operator Buffer() const;
//! \brief Discard the first `n` bytes of the string (does not require a copy or move)
void remove_prefix(size_t n);
//! \brief Size of the string
size_t size() const;
//! \brief Make a copy to a new std::string
std::string concatenate() const;
};
//! \brief A non-owning temporary view (similar to std::string_view) of a discontiguous string
class BufferViewList {
std::deque<std::string_view> _views{};
public:
//! \name Constructors
//!@{
//! \brief Construct from a std::string
BufferViewList(const std::string &str) : BufferViewList(std::string_view(str)) {}
//! \brief Construct from a C string (must be NULL-terminated)
BufferViewList(const char *s) : BufferViewList(std::string_view(s)) {}
//! \brief Construct from a BufferList
BufferViewList(const BufferList &buffers);
//! \brief Construct from a std::string_view
BufferViewList(std::string_view str) { _views.push_back({const_cast<char *>(str.data()), str.size()}); }
//!@}
//! \brief Discard the first `n` bytes of the string (does not require a copy or move)
void remove_prefix(size_t n);
//! \brief Size of the string
size_t size() const;
//! \brief Convert to a vector of `iovec` structures
//! \note used for system calls that write discontiguous buffers,
//! e.g. [writev(2)](\ref man2::writev) and [sendmsg(2)](\ref man2::sendmsg)
std::vector<iovec> as_iovecs() const;
};
#endif // SPONGE_LIBSPONGE_BUFFER_HH
#include "eventloop.hh"
#include "util.hh"
#include <cerrno>
#include <stdexcept>
#include <system_error>
#include <utility>
#include <vector>
using namespace std;
unsigned int EventLoop::Rule::service_count() const {
return direction == Direction::In ? fd.read_count() : fd.write_count();
}
//! \param[in] fd is the FileDescriptor to be polled
//! \param[in] direction indicates whether to poll for reading (Direction::In) or writing (Direction::Out)
//! \param[in] callback is called when `fd` is ready.
//! \param[in] interest is called by EventLoop::wait_next_event. If it returns `true`, `fd` will
//! be polled, otherwise `fd` will be ignored only for this execution of `wait_next_event.
//! \param[in] cancel is called when the rule is cancelled (e.g. on hangup, EOF, or closure).
void EventLoop::add_rule(const FileDescriptor &fd,
const Direction direction,
const CallbackT &callback,
const InterestT &interest,
const CallbackT &cancel) {
_rules.push_back({fd.duplicate(), direction, callback, interest, cancel});
}
//! \param[in] timeout_ms is the timeout value passed to [poll(2)](\ref man2::poll); `wait_next_event`
//! returns Result::Timeout if no fd is ready after the timeout expires.
//! \returns Eventloop::Result indicating success, timeout, or no more Rule objects to poll.
//!
//! For each Rule, this function first calls Rule::interest; if `true`, Rule::fd is added to the
//! list of file descriptors to be polled for readability (if Rule::direction == Direction::In) or
//! writability (if Rule::direction == Direction::Out) unless Rule::fd has reached EOF, in which case
//! the Rule is canceled (i.e., deleted from EventLoop::_rules).
//!
//! Next, this function calls [poll(2)](\ref man2::poll) with timeout value `timeout_ms`.
//!
//! Then, for each ready file descriptor, this function calls Rule::callback. If fd reaches EOF or
//! if the Rule was registered using EventLoop::add_cancelable_rule and Rule::callback returns true,
//! this Rule is canceled.
//!
//! If an error occurs during polling, this function throws a std::runtime_error.
//!
//! If a [signal(7)](\ref man7::signal) was caught during polling or if EventLoop::_rules becomes empty,
//! this function returns Result::Exit.
//!
//! If a timeout occurred while polling (i.e., no fd became ready), this function returns Result::Timeout.
//!
//! Otherwise, this function returns Result::Success.
//!
//! \b IMPORTANT: every call to Rule::callback must read from or write to Rule::fd, or the `interest`
//! callback must stop returning true after the callback completes.
//! If none of these conditions occur, EventLoop::wait_next_event will throw std::runtime_error. This is
//! because [poll(2)](\ref man2::poll) is level triggered, so failing to act on a ready file descriptor
//! will result in a busy loop (poll returns on a ready file descriptor; file descriptor is not read or
//! written, so it is still ready; the next call to poll will immediately return).
EventLoop::Result EventLoop::wait_next_event(const int timeout_ms) {
vector<pollfd> pollfds{};
pollfds.reserve(_rules.size());
bool something_to_poll = false;
// set up the pollfd for each rule
for (auto it = _rules.cbegin(); it != _rules.cend();) { // NOTE: it gets erased or incremented in loop body
const auto &this_rule = *it;
if (this_rule.direction == Direction::In && this_rule.fd.eof()) {
// no more reading on this rule, it's reached eof
this_rule.cancel();
it = _rules.erase(it);
continue;
}
if (this_rule.fd.closed()) {
this_rule.cancel();
it = _rules.erase(it);
continue;
}
if (this_rule.interest()) {
pollfds.push_back({this_rule.fd.fd_num(), static_cast<short>(this_rule.direction), 0});
something_to_poll = true;
} else {
pollfds.push_back({this_rule.fd.fd_num(), 0, 0}); // placeholder --- we still want errors
}
++it;
}
// quit if there is nothing left to poll
if (not something_to_poll) {
return Result::Exit;
}
// call poll -- wait until one of the fds satisfies one of the rules (writeable/readable)
try {
if (0 == SystemCall("poll", ::poll(pollfds.data(), pollfds.size(), timeout_ms))) {
return Result::Timeout;
}
} catch (unix_error const &e) {
if (e.code().value() == EINTR) {
return Result::Exit;
}
}
// go through the poll results
for (auto [it, idx] = make_pair(_rules.begin(), size_t(0)); it != _rules.end(); ++idx) {
const auto &this_pollfd = pollfds[idx];
const auto poll_error = static_cast<bool>(this_pollfd.revents & (POLLERR | POLLNVAL));
if (poll_error) {
throw runtime_error("EventLoop: error on polled file descriptor");
}
const auto &this_rule = *it;
const auto poll_ready = static_cast<bool>(this_pollfd.revents & this_pollfd.events);
const auto poll_hup = static_cast<bool>(this_pollfd.revents & POLLHUP);
if (poll_hup && this_pollfd.events && !poll_ready) {
// if we asked for the status, and the _only_ condition was a hangup, this FD is defunct:
// - if it was POLLIN and nothing is readable, no more will ever be readable
// - if it was POLLOUT, it will not be writable again
this_rule.cancel();
it = _rules.erase(it);
continue;
}
if (poll_ready) {
// we only want to call callback if revents includes the event we asked for
const auto count_before = this_rule.service_count();
this_rule.callback();
// only check for busy wait if we're not canceling or exiting
if (count_before == this_rule.service_count() and this_rule.interest()) {
throw runtime_error(
"EventLoop: busy wait detected: callback did not read/write fd and is still interested");
}
}
++it; // if we got here, it means we didn't call _rules.erase()
}
return Result::Success;
}
#ifndef SPONGE_LIBSPONGE_EVENTLOOP_HH
#define SPONGE_LIBSPONGE_EVENTLOOP_HH
#include "file_descriptor.hh"
#include <cstdlib>
#include <functional>
#include <list>
#include <poll.h>
//! Waits for events on file descriptors and executes corresponding callbacks.
class EventLoop {
public:
//! Indicates interest in reading (In) or writing (Out) a polled fd.
enum class Direction : short {
In = POLLIN, //!< Callback will be triggered when Rule::fd is readable.
Out = POLLOUT //!< Callback will be triggered when Rule::fd is writable.
};
private:
using CallbackT = std::function<void(void)>; //!< Callback for ready Rule::fd
using InterestT = std::function<bool(void)>; //!< `true` return indicates Rule::fd should be polled.
//! \brief Specifies a condition and callback that an EventLoop should handle.
//! \details Created by calling EventLoop::add_rule() or EventLoop::add_cancelable_rule().
class Rule {
public:
FileDescriptor fd; //!< FileDescriptor to monitor for activity.
Direction direction; //!< Direction::In for reading from fd, Direction::Out for writing to fd.
CallbackT callback; //!< A callback that reads or writes fd.
InterestT interest; //!< A callback that returns `true` whenever fd should be polled.
CallbackT cancel; //!< A callback that is called when the rule is cancelled (e.g. on hangup)
//! Returns the number of times fd has been read or written, depending on the value of Rule::direction.
//! \details This function is used internally by EventLoop; you will not need to call it
unsigned int service_count() const;
};
std::list<Rule> _rules{}; //!< All rules that have been added and not canceled.
public:
//! Returned by each call to EventLoop::wait_next_event.
enum class Result {
Success, //!< At least one Rule was triggered.
Timeout, //!< No rules were triggered before timeout.
Exit //!< All rules have been canceled or were uninterested; make no further calls to EventLoop::wait_next_event.
};
//! Add a rule whose callback will be called when `fd` is ready in the specified Direction.
void add_rule(const FileDescriptor &fd,
const Direction direction,
const CallbackT &callback,
const InterestT &interest = [] { return true; },
const CallbackT &cancel = [] {});
//! Calls [poll(2)](\ref man2::poll) and then executes callback for each ready fd.
Result wait_next_event(const int timeout_ms);
};
using Direction = EventLoop::Direction;
//! \class EventLoop
//!
//! An EventLoop holds a std::list of Rule objects. Each time EventLoop::wait_next_event is
//! executed, the EventLoop uses the Rule objects to construct a call to [poll(2)](\ref man2::poll).
//!
//! When a Rule is installed using EventLoop::add_rule, it will be polled for the specified Rule::direction
//! whenver the Rule::interest callback returns `true`, until Rule::fd is no longer readable
//! (for Rule::direction == Direction::In) or writable (for Rule::direction == Direction::Out).
//! Once this occurs, the Rule is canceled, i.e., the EventLoop deletes it.
//!
//! A Rule installed using EventLoop::add_cancelable_rule will be polled and canceled under the
//! same conditions, with the additional condition that if Rule::callback returns `true`, the
//! Rule will be canceled.
#endif // SPONGE_LIBSPONGE_EVENTLOOP_HH
#include "file_descriptor.hh"
#include "util.hh"
#include <algorithm>
#include <fcntl.h>
#include <iostream>
#include <stdexcept>
#include <sys/uio.h>
#include <unistd.h>
using namespace std;
//! \param[in] fd is the file descriptor number returned by [open(2)](\ref man2::open) or similar
FileDescriptor::FDWrapper::FDWrapper(const int fd) : _fd(fd) {
if (fd < 0) {
throw runtime_error("invalid fd number:" + to_string(fd));
}
}
void FileDescriptor::FDWrapper::close() {
SystemCall("close", ::close(_fd));
_eof = _closed = true;
}
FileDescriptor::FDWrapper::~FDWrapper() {
try {
if (_closed) {
return;
}
close();
} catch (const exception &e) {
// don't throw an exception from the destructor
std::cerr << "Exception destructing FDWrapper: " << e.what() << std::endl;
}
}
//! \param[in] fd is the file descriptor number returned by [open(2)](\ref man2::open) or similar
FileDescriptor::FileDescriptor(const int fd) : _internal_fd(make_shared<FDWrapper>(fd)) {}
//! Private constructor used by duplicate()
FileDescriptor::FileDescriptor(shared_ptr<FDWrapper> other_shared_ptr) : _internal_fd(move(other_shared_ptr)) {}
//! \returns a copy of this FileDescriptor
FileDescriptor FileDescriptor::duplicate() const { return FileDescriptor(_internal_fd); }
//! \param[in] limit is the maximum number of bytes to read; fewer bytes may be returned
//! \param[out] str is the string to be read
void FileDescriptor::read(std::string &str, const size_t limit) {
constexpr size_t BUFFER_SIZE = 1024 * 1024; // maximum size of a read
const size_t size_to_read = min(BUFFER_SIZE, limit);
str.resize(size_to_read);
ssize_t bytes_read = SystemCall("read", ::read(fd_num(), str.data(), size_to_read));
if (limit > 0 && bytes_read == 0) {
_internal_fd->_eof = true;
}
if (bytes_read > static_cast<ssize_t>(size_to_read)) {
throw runtime_error("read() read more than requested");
}
str.resize(bytes_read);
register_read();
}
//! \param[in] limit is the maximum number of bytes to read; fewer bytes may be returned
//! \returns a vector of bytes read
string FileDescriptor::read(const size_t limit) {
string ret;
read(ret, limit);
return ret;
}
size_t FileDescriptor::write(BufferViewList buffer, const bool write_all) {
size_t total_bytes_written = 0;
do {
auto iovecs = buffer.as_iovecs();
const ssize_t bytes_written = SystemCall("writev", ::writev(fd_num(), iovecs.data(), iovecs.size()));
if (bytes_written == 0 and buffer.size() != 0) {
throw runtime_error("write returned 0 given non-empty input buffer");
}
if (bytes_written > ssize_t(buffer.size())) {
throw runtime_error("write wrote more than length of input buffer");
}
register_write();
buffer.remove_prefix(bytes_written);
total_bytes_written += bytes_written;
} while (write_all and buffer.size());
return total_bytes_written;
}
void FileDescriptor::set_blocking(const bool blocking_state) {
int flags = SystemCall("fcntl", fcntl(fd_num(), F_GETFL));
if (blocking_state) {
flags ^= (flags & O_NONBLOCK);
} else {
flags |= O_NONBLOCK;
}
SystemCall("fcntl", fcntl(fd_num(), F_SETFL, flags));
}
#ifndef SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH
#define SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH
#include "buffer.hh"
#include <array>
#include <cstddef>
#include <limits>
#include <memory>
//! A reference-counted handle to a file descriptor
class FileDescriptor {
//! \brief A handle on a kernel file descriptor.
//! \details FileDescriptor objects contain a std::shared_ptr to a FDWrapper.
class FDWrapper {
public:
int _fd; //!< The file descriptor number returned by the kernel
bool _eof = false; //!< Flag indicating whether FDWrapper::_fd is at EOF
bool _closed = false; //!< Flag indicating whether FDWrapper::_fd has been closed
unsigned _read_count = 0; //!< The number of times FDWrapper::_fd has been read
unsigned _write_count = 0; //!< The numberof times FDWrapper::_fd has been written
//! Construct from a file descriptor number returned by the kernel
explicit FDWrapper(const int fd);
//! Closes the file descriptor upon destruction
~FDWrapper();
//! Calls [close(2)](\ref man2::close) on FDWrapper::_fd
void close();
//! \name
//! An FDWrapper cannot be copied or moved
//!@{
FDWrapper(const FDWrapper &other) = delete;
FDWrapper &operator=(const FDWrapper &other) = delete;
FDWrapper(FDWrapper &&other) = delete;
FDWrapper &operator=(FDWrapper &&other) = delete;
//!@}
};
//! A reference-counted handle to a shared FDWrapper
std::shared_ptr<FDWrapper> _internal_fd;
// private constructor used to duplicate the FileDescriptor (increase the reference count)
explicit FileDescriptor(std::shared_ptr<FDWrapper> other_shared_ptr);
protected:
void register_read() { ++_internal_fd->_read_count; } //!< increment read count
void register_write() { ++_internal_fd->_write_count; } //!< increment write count
public:
//! Construct from a file descriptor number returned by the kernel
explicit FileDescriptor(const int fd);
//! Free the std::shared_ptr; the FDWrapper destructor calls close() when the refcount goes to zero.
~FileDescriptor() = default;
//! Read up to `limit` bytes
std::string read(const size_t limit = std::numeric_limits<size_t>::max());
//! Read up to `limit` bytes into `str` (caller can allocate storage)
void read(std::string &str, const size_t limit = std::numeric_limits<size_t>::max());
//! Write a string, possibly blocking until all is written
size_t write(const char *str, const bool write_all = true) { return write(BufferViewList(str), write_all); }
//! Write a string, possibly blocking until all is written
size_t write(const std::string &str, const bool write_all = true) { return write(BufferViewList(str), write_all); }
//! Write a buffer (or list of buffers), possibly blocking until all is written
size_t write(BufferViewList buffer, const bool write_all = true);
//! Close the underlying file descriptor
void close() { _internal_fd->close(); }
//! Copy a FileDescriptor explicitly, increasing the FDWrapper refcount
FileDescriptor duplicate() const;
//! Set blocking(true) or non-blocking(false)
void set_blocking(const bool blocking_state);
//! \name FDWrapper accessors
//!@{
int fd_num() const { return _internal_fd->_fd; } //!< \brief underlying descriptor number
bool eof() const { return _internal_fd->_eof; } //!< \brief EOF flag state
bool closed() const { return _internal_fd->_closed; } //!< \brief closed flag state
unsigned int read_count() const { return _internal_fd->_read_count; } //!< \brief number of reads
unsigned int write_count() const { return _internal_fd->_write_count; } //!< \brief number of writes
//!@}
//! \name Copy/move constructor/assignment operators
//! FileDescriptor can be moved, but cannot be copied (but see duplicate())
//!@{
FileDescriptor(const FileDescriptor &other) = delete; //!< \brief copy construction is forbidden
FileDescriptor &operator=(const FileDescriptor &other) = delete; //!< \brief copy assignment is forbidden
FileDescriptor(FileDescriptor &&other) = default; //!< \brief move construction is allowed
FileDescriptor &operator=(FileDescriptor &&other) = default; //!< \brief move assignment is allowed
//!@}
};
//! \class FileDescriptor
//! In addition, FileDescriptor tracks EOF state and calls to FileDescriptor::read and
//! FileDescriptor::write, which EventLoop uses to detect busy loop conditions.
//!
//! For an example of FileDescriptor use, see the EventLoop class documentation.
#endif // SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH
#include "parser.hh"
using namespace std;
//! \param[in] r is the ParseResult to show
//! \returns a string representation of the ParseResult
string as_string(const ParseResult r) {
static constexpr const char *_names[] = {
"NoError",
"BadChecksum",
"PacketTooShort",
"WrongIPVersion",
"HeaderTooShort",
"TruncatedPacket",
};
return _names[static_cast<size_t>(r)];
}
void NetParser::_check_size(const size_t size) {
if (size > _buffer.size()) {
set_error(ParseResult::PacketTooShort);
}
}
template <typename T>
T NetParser::_parse_int() {
constexpr size_t len = sizeof(T);
_check_size(len);
if (error()) {
return 0;
}
T ret = 0;
for (size_t i = 0; i < len; i++) {
ret <<= 8;
ret += uint8_t(_buffer.at(i));
}
_buffer.remove_prefix(len);
return ret;
}
void NetParser::remove_prefix(const size_t n) {
_check_size(n);
if (error()) {
return;
}
_buffer.remove_prefix(n);
}
template <typename T>
void NetUnparser::_unparse_int(string &s, T val) {
constexpr size_t len = sizeof(T);
for (size_t i = 0; i < len; ++i) {
const uint8_t the_byte = (val >> ((len - i - 1) * 8)) & 0xff;
s.push_back(the_byte);
}
}
uint32_t NetParser::u32() { return _parse_int<uint32_t>(); }
uint16_t NetParser::u16() { return _parse_int<uint16_t>(); }
uint8_t NetParser::u8() { return _parse_int<uint8_t>(); }
void NetUnparser::u32(string &s, const uint32_t val) { return _unparse_int<uint32_t>(s, val); }
void NetUnparser::u16(string &s, const uint16_t val) { return _unparse_int<uint16_t>(s, val); }
void NetUnparser::u8(string &s, const uint8_t val) { return _unparse_int<uint8_t>(s, val); }
#ifndef SPONGE_LIBSPONGE_PARSER_HH
#define SPONGE_LIBSPONGE_PARSER_HH
#include "buffer.hh"
#include <cstdint>
#include <cstdlib>
#include <string>
#include <utility>
//! The result of parsing or unparsing an IP datagram or TCP segment
enum class ParseResult {
NoError = 0, //!< Success
BadChecksum, //!< Bad checksum
PacketTooShort, //!< Not enough data to finish parsing
WrongIPVersion, //!< Got a version of IP other than 4
HeaderTooShort, //!< Header length is shorter than minimum required
TruncatedPacket, //!< Packet length is shorter than header claims
};
//! Output a string representation of a ParseResult
std::string as_string(const ParseResult r);
class NetParser {
private:
Buffer _buffer;
ParseResult _error = ParseResult::NoError; //!< Result of parsing so far
//! Check that there is sufficient data to parse the next token
void _check_size(const size_t size);
//! Generic integer parsing method (used by u32, u16, u8)
template <typename T>
T _parse_int();
public:
NetParser(Buffer buffer) : _buffer(buffer) {}
Buffer buffer() const { return _buffer; }
//! Get the current value stored in BaseParser::_error
ParseResult get_error() const { return _error; }
//! \brief Set BaseParser::_error
//! \param[in] res is the value to store in BaseParser::_error
void set_error(ParseResult res) { _error = res; }
//! Returns `true` if there has been an error
bool error() const { return get_error() != ParseResult::NoError; }
//! Parse a 32-bit integer in network byte order from the data stream
uint32_t u32();
//! Parse a 16-bit integer in network byte order from the data stream
uint16_t u16();
//! Parse an 8-bit integer in network byte order from the data stream
uint8_t u8();
//! Remove n bytes from the buffer
void remove_prefix(const size_t n);
};
struct NetUnparser {
template <typename T>
static void _unparse_int(std::string &s, T val);
//! Write a 32-bit integer into the data stream in network byte order
static void u32(std::string &s, const uint32_t val);
//! Write a 16-bit integer into the data stream in network byte order
static void u16(std::string &s, const uint16_t val);
//! Write an 8-bit integer into the data stream in network byte order
static void u8(std::string &s, const uint8_t val);
};
#endif // SPONGE_LIBSPONGE_PARSER_HH
This diff is collapsed.
#ifndef SPONGE_LIBSPONGE_SOCKET_HH
#define SPONGE_LIBSPONGE_SOCKET_HH
#include "address.hh"
#include "file_descriptor.hh"
#include <cstdint>
#include <functional>
#include <string>
#include <sys/socket.h>
//! \brief Base class for network sockets (TCP, UDP, etc.)
//! \details Socket is generally used via a subclass. See TCPSocket and UDPSocket for usage examples.
class Socket : public FileDescriptor {
private:
//! Get the local or peer address the socket is connected to
Address get_address(const std::string &name_of_function,
const std::function<int(int, sockaddr *, socklen_t *)> &function) const;
protected:
//! Construct via [socket(2)](\ref man2::socket)
Socket(const int domain, const int type);
//! Construct from a file descriptor.
Socket(FileDescriptor &&fd, const int domain, const int type);
//! Wrapper around [setsockopt(2)](\ref man2::setsockopt)
template <typename option_type>
void setsockopt(const int level, const int option, const option_type &option_value);
public:
//! Bind a socket to a specified address with [bind(2)](\ref man2::bind), usually for listen/accept
void bind(const Address &address);
//! Connect a socket to a specified peer address with [connect(2)](\ref man2::connect)
void connect(const Address &address);
//! Shut down a socket via [shutdown(2)](\ref man2::shutdown)
void shutdown(const int how);
//! Get local address of socket with [getsockname(2)](\ref man2::getsockname)
Address local_address() const;
//! Get peer address of socket with [getpeername(2)](\ref man2::getpeername)
Address peer_address() const;
//! Allow local address to be reused sooner via [SO_REUSEADDR](\ref man7::socket)
void set_reuseaddr();
};
//! A wrapper around [UDP sockets](\ref man7::udp)
class UDPSocket : public Socket {
protected:
//! \brief Construct from FileDescriptor (used by TCPOverUDPSocketAdapter)
//! \param[in] fd is the FileDescriptor from which to construct
explicit UDPSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_INET, SOCK_DGRAM) {}
public:
//! Default: construct an unbound, unconnected UDP socket
UDPSocket() : Socket(AF_INET, SOCK_DGRAM) {}
//! Returned by UDPSocket::recv; carries received data and information about the sender
struct received_datagram {
Address source_address; //!< Address from which this datagram was received
std::string payload; //!< UDP datagram payload
};
//! Receive a datagram and the Address of its sender
received_datagram recv(const size_t mtu = 65536);
//! Receive a datagram and the Address of its sender (caller can allocate storage)
void recv(received_datagram &datagram, const size_t mtu = 65536);
//! Send a datagram to specified Address
void sendto(const Address &destination, const BufferViewList &payload);
//! Send datagram to the socket's connected address (must call connect() first)
void send(const BufferViewList &payload);
};
//! \class UDPSocket
//! Functions in this class are essentially wrappers over their POSIX eponyms.
//!
//! Example:
//!
//! \include socket_example_1.cc
//! A wrapper around [TCP sockets](\ref man7::tcp)
class TCPSocket : public Socket {
private:
//! \brief Construct from FileDescriptor (used by accept())
//! \param[in] fd is the FileDescriptor from which to construct
explicit TCPSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_INET, SOCK_STREAM) {}
public:
//! Default: construct an unbound, unconnected TCP socket
TCPSocket() : Socket(AF_INET, SOCK_STREAM) {}
//! Mark a socket as listening for incoming connections
void listen(const int backlog = 16);
//! Accept a new incoming connection
TCPSocket accept();
};
//! \class TCPSocket
//! Functions in this class are essentially wrappers over their POSIX eponyms.
//!
//! Example:
//!
//! \include socket_example_2.cc
//! A wrapper around [Unix-domain stream sockets](\ref man7::unix)
class LocalStreamSocket : public Socket {
public:
//! Construct from a file descriptor
explicit LocalStreamSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_UNIX, SOCK_STREAM) {}
};
//! \class LocalStreamSocket
//! Example:
//!
//! \include socket_example_3.cc
#endif // SPONGE_LIBSPONGE_SOCKET_HH
#include "tun.hh"
#include "util.hh"
#include <cstring>
#include <fcntl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <sys/ioctl.h>
static constexpr const char *CLONEDEV = "/dev/net/tun";
using namespace std;
//! \param[in] devname is the name of the TUN device, specified at its creation.
//!
//! To create a TUN device, you should already have run
//!
//! ip tuntap add mode tun user `username` name `devname`
//!
//! as root before calling this function.
TunFD::TunFD(const string &devname) : FileDescriptor(SystemCall("open", open(CLONEDEV, O_RDWR))) {
struct ifreq tun_req {};
tun_req.ifr_flags = IFF_TUN | IFF_NO_PI; // tun device with no packetinfo
// copy devname to ifr_name, making sure to null terminate
strncpy(static_cast<char *>(tun_req.ifr_name), devname.data(), IFNAMSIZ - 1);
tun_req.ifr_name[IFNAMSIZ - 1] = '\0';
SystemCall("ioctl", ioctl(fd_num(), TUNSETIFF, static_cast<void *>(&tun_req)));
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment