├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── README.md ├── apps ├── CMakeLists.txt ├── bidirectional_stream_copy.cc ├── bidirectional_stream_copy.hh ├── bouncer.cc ├── lab7.cc ├── network_simulator.cc ├── tcp_benchmark.cc ├── tcp_ip_ethernet.cc ├── tcp_ipv4.cc ├── tcp_native.cc ├── tcp_udp.cc ├── tun.cc ├── udp_tcpdump.cc └── webget.cc ├── compile_commands.json ├── doctests ├── CMakeLists.txt ├── address_dt.cc ├── address_example_1.cc ├── address_example_2.cc ├── address_example_3.cc ├── parser_dt.cc ├── parser_example.cc ├── socket_dt.cc ├── socket_example_1.cc ├── socket_example_2.cc └── socket_example_3.cc ├── etc ├── Doxyfile.in ├── build_defs.cmake ├── build_type.cmake ├── cflags.cmake ├── clang_format.cmake ├── clang_tidy.cmake ├── cppcheck.cmake ├── cppreference-doxygen-web.tag.xml ├── doxygen.cmake ├── linux-man-doxygen-web.tag.xml ├── rfc-doxygen-web.tag.xml ├── sponge_doxygen.css ├── sponge_small.png ├── tests.cmake └── tunconfig ├── labs_pdf ├── lab0.pdf ├── lab1.pdf ├── lab2.pdf ├── lab3.pdf ├── lab4.pdf ├── lab5.pdf ├── lab6.pdf └── lab7.pdf ├── libsponge ├── CMakeLists.txt ├── byte_stream.cc ├── byte_stream.hh ├── network_interface.cc ├── network_interface.hh ├── router.cc ├── router.hh ├── stream_reassembler.cc ├── stream_reassembler.hh ├── tap.sh ├── tcp_connection.cc ├── tcp_connection.hh ├── tcp_helpers │ ├── arp_message.cc │ ├── arp_message.hh │ ├── ethernet_frame.cc │ ├── ethernet_frame.hh │ ├── ethernet_header.cc │ ├── ethernet_header.hh │ ├── fd_adapter.cc │ ├── fd_adapter.hh │ ├── ipv4_datagram.cc │ ├── ipv4_datagram.hh │ ├── ipv4_header.cc │ ├── ipv4_header.hh │ ├── lossy_fd_adapter.hh │ ├── tcp_config.hh │ ├── tcp_header.cc │ ├── tcp_header.hh │ ├── tcp_over_ip.cc │ ├── tcp_over_ip.hh │ ├── tcp_segment.cc │ ├── tcp_segment.hh │ ├── tcp_sponge_socket.cc │ ├── tcp_sponge_socket.hh │ ├── tcp_state.cc │ ├── tcp_state.hh │ ├── tuntap_adapter.cc │ └── tuntap_adapter.hh ├── tcp_receiver.cc ├── tcp_receiver.hh ├── tcp_sender.cc ├── tcp_sender.hh ├── util │ ├── address.cc │ ├── address.hh │ ├── buffer.cc │ ├── buffer.hh │ ├── eventloop.cc │ ├── eventloop.hh │ ├── file_descriptor.cc │ ├── file_descriptor.hh │ ├── parser.cc │ ├── parser.hh │ ├── socket.cc │ ├── socket.hh │ ├── tun.cc │ ├── tun.hh │ ├── util.cc │ └── util.hh ├── wrapping_integers.cc └── wrapping_integers.hh ├── tap.sh ├── tests ├── CMakeLists.txt ├── byte_stream_capacity.cc ├── byte_stream_construction.cc ├── byte_stream_many_writes.cc ├── byte_stream_one_write.cc ├── byte_stream_test_harness.cc ├── byte_stream_test_harness.hh ├── byte_stream_two_writes.cc ├── fsm_ack_rst.cc ├── fsm_ack_rst_relaxed.cc ├── fsm_ack_rst_win.cc ├── fsm_ack_rst_win_relaxed.cc ├── fsm_active_close.cc ├── fsm_connect.cc ├── fsm_connect_relaxed.cc ├── fsm_listen.cc ├── fsm_listen_relaxed.cc ├── fsm_loopback.cc ├── fsm_loopback_win.cc ├── fsm_passive_close.cc ├── fsm_reorder.cc ├── fsm_retx.cc ├── fsm_retx.hh ├── fsm_retx_relaxed.cc ├── fsm_retx_win.cc ├── fsm_stream_reassembler_cap.cc ├── fsm_stream_reassembler_dup.cc ├── fsm_stream_reassembler_harness.hh ├── fsm_stream_reassembler_holes.cc ├── fsm_stream_reassembler_many.cc ├── fsm_stream_reassembler_overlapping.cc ├── fsm_stream_reassembler_seq.cc ├── fsm_stream_reassembler_single.cc ├── fsm_stream_reassembler_win.cc ├── fsm_winsize.cc ├── ipv4_parser.cc ├── ipv4_parser.data ├── net_interface.cc ├── network_interface_test_harness.cc ├── network_interface_test_harness.hh ├── receiver_harness.hh ├── recv_close.cc ├── recv_connect.cc ├── recv_reorder.cc ├── recv_special.cc ├── recv_transmit.cc ├── recv_window.cc ├── send_ack.cc ├── send_close.cc ├── send_connect.cc ├── send_equivalence_checker.cc ├── send_equivalence_checker.hh ├── send_extra.cc ├── send_retx.cc ├── send_transmit.cc ├── send_window.cc ├── sender_harness.hh ├── string_conversions.hh ├── tcp_expectation.hh ├── tcp_expectation_forward.hh ├── tcp_fsm_test_harness.cc ├── tcp_fsm_test_harness.hh ├── tcp_parser.cc ├── test_err_if.hh ├── test_should_be.hh ├── test_utils.hh ├── test_utils_ipv4.hh ├── webget_t.sh ├── wrapping_integers_cmp.cc ├── wrapping_integers_roundtrip.cc ├── wrapping_integers_unwrap.cc └── wrapping_integers_wrap.cc ├── tun.sh ├── txrx.sh └── writeups ├── lab0.md ├── lab1.md ├── lab2.md ├── lab3.md ├── lab4.md ├── lab5.md ├── lab6.md └── lab7.md /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | AllowAllParametersOfDeclarationOnNextLine: false 5 | AlwaysBreakTemplateDeclarations: true 6 | BinPackArguments: false 7 | BinPackParameters: false 8 | BreakConstructorInitializers: BeforeComma 9 | ColumnLimit: 120 10 | CommentPragmas: '^(!|NOLINT)' 11 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 12 | IncludeBlocks: Regroup 13 | IncludeCategories: 14 | - Regex: '^<.*' 15 | Priority: 2 16 | - Regex: '.*' 17 | Priority: 1 18 | IncludeIsMainRegex: '(_dt|_win)?$' 19 | IndentCaseLabels: true 20 | IndentWidth: 4 21 | KeepEmptyLinesAtTheStartOfBlocks: false 22 | PenaltyReturnTypeOnItsOwnLine: 200 23 | SpacesBeforeTrailingComments: 2 24 | TabWidth: 4 25 | UseTab: Never 26 | ... 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.ccls-cache 3 | /.vscode 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8.5) 2 | cmake_policy (SET CMP0054 NEW) 3 | project (Sponge) 4 | 5 | include (etc/build_defs.cmake) 6 | include (etc/build_type.cmake) 7 | include (etc/cflags.cmake) 8 | 9 | include (etc/doxygen.cmake) 10 | 11 | include (etc/clang_format.cmake) 12 | include (etc/clang_tidy.cmake) 13 | include (etc/cppcheck.cmake) 14 | 15 | include_directories ("${PROJECT_SOURCE_DIR}/libsponge/util") 16 | include_directories ("${PROJECT_SOURCE_DIR}/libsponge/tcp_helpers") 17 | include_directories ("${PROJECT_SOURCE_DIR}/libsponge") 18 | 19 | add_subdirectory ("${PROJECT_SOURCE_DIR}/libsponge") 20 | 21 | add_subdirectory ("${PROJECT_SOURCE_DIR}/apps") 22 | 23 | add_subdirectory ("${PROJECT_SOURCE_DIR}/tests") 24 | 25 | add_subdirectory ("${PROJECT_SOURCE_DIR}/doctests") 26 | 27 | include (etc/tests.cmake) 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | For build prereqs, see [the CS144 VM setup instructions](https://web.stanford.edu/class/cs144/vm_howto). 2 | 3 | ## Sponge quickstart 4 | 5 | To set up your build directory: 6 | 7 | $ mkdir -p /build 8 | $ cd /build 9 | $ cmake .. 10 | 11 | **Note:** all further commands listed below should be run from the `build` dir. 12 | 13 | To build: 14 | 15 | $ make 16 | 17 | You can use the `-j` switch to build in parallel, e.g., 18 | 19 | $ make -j$(nproc) 20 | 21 | To test (after building; make sure you've got the [build prereqs](https://web.stanford.edu/class/cs144/vm_howto) installed!) 22 | 23 | $ make check_labN *(replacing N with a checkpoint number)* 24 | 25 | The first time you run `make check_lab...`, it will run `sudo` to configure two 26 | [TUN](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) devices for use during 27 | testing. 28 | 29 | ### build options 30 | 31 | You can specify a different compiler when you run cmake: 32 | 33 | $ CC=clang CXX=clang++ cmake .. 34 | 35 | You can also specify `CLANG_TIDY=` or `CLANG_FORMAT=` (see "other useful targets", below). 36 | 37 | Sponge's build system supports several different build targets. By default, cmake chooses the `Release` 38 | target, which enables the usual optimizations. The `Debug` target enables debugging and reduces the 39 | level of optimization. To choose the `Debug` target: 40 | 41 | $ cmake .. -DCMAKE_BUILD_TYPE=Debug 42 | 43 | The following targets are supported: 44 | 45 | - `Release` - optimizations 46 | - `Debug` - debug symbols and `-Og` 47 | - `RelASan` - release build with [ASan](https://en.wikipedia.org/wiki/AddressSanitizer) and 48 | [UBSan](https://developers.redhat.com/blog/2014/10/16/gcc-undefined-behavior-sanitizer-ubsan/) 49 | - `RelTSan` - release build with 50 | [ThreadSan](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Thread_Sanitizer) 51 | - `DebugASan` - debug build with ASan and UBSan 52 | - `DebugTSan` - debug build with ThreadSan 53 | 54 | Of course, you can combine all of the above, e.g., 55 | 56 | $ CLANG_TIDY=clang-tidy-6.0 CXX=clang++-6.0 .. -DCMAKE_BUILD_TYPE=Debug 57 | 58 | **Note:** if you want to change `CC`, `CXX`, `CLANG_TIDY`, or `CLANG_FORMAT`, you need to remove 59 | `build/CMakeCache.txt` and re-run cmake. (This isn't necessary for `CMAKE_BUILD_TYPE`.) 60 | 61 | ### other useful targets 62 | 63 | To generate documentation (you'll need `doxygen`; output will be in `build/doc/`): 64 | 65 | $ make doc 66 | 67 | To format (you'll need `clang-format`): 68 | 69 | $ make format 70 | 71 | To see all available targets, 72 | 73 | $ make help 74 | -------------------------------------------------------------------------------- /apps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library (stream_copy STATIC bidirectional_stream_copy.cc) 2 | 3 | add_sponge_exec (udp_tcpdump ${LIBPCAP}) 4 | add_sponge_exec (tcp_native stream_copy) 5 | add_sponge_exec (tun) 6 | add_sponge_exec (tcp_udp stream_copy) 7 | add_sponge_exec (tcp_ipv4 stream_copy) 8 | add_sponge_exec (tcp_ip_ethernet stream_copy) 9 | add_sponge_exec (webget) 10 | add_sponge_exec (tcp_benchmark) 11 | add_sponge_exec (network_simulator) 12 | add_sponge_exec (lab7 stream_copy) 13 | add_sponge_exec (bouncer) 14 | -------------------------------------------------------------------------------- /apps/bidirectional_stream_copy.cc: -------------------------------------------------------------------------------- 1 | #include "bidirectional_stream_copy.hh" 2 | 3 | #include "byte_stream.hh" 4 | #include "eventloop.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | void bidirectional_stream_copy(Socket &socket) { 13 | constexpr size_t max_copy_length = 65536; 14 | constexpr size_t buffer_size = 1048576; 15 | 16 | EventLoop _eventloop{}; 17 | FileDescriptor _input{STDIN_FILENO}; 18 | FileDescriptor _output{STDOUT_FILENO}; 19 | ByteStream _outbound{buffer_size}; 20 | ByteStream _inbound{buffer_size}; 21 | bool _outbound_shutdown{false}; 22 | bool _inbound_shutdown{false}; 23 | 24 | socket.set_blocking(false); 25 | _input.set_blocking(false); 26 | _output.set_blocking(false); 27 | 28 | // rule 1: read from stdin into outbound byte stream 29 | _eventloop.add_rule( 30 | _input, 31 | Direction::In, 32 | [&] { 33 | _outbound.write(_input.read(_outbound.remaining_capacity())); 34 | if (_input.eof()) { 35 | _outbound.end_input(); 36 | } 37 | }, 38 | [&] { return (not _outbound.error()) and (_outbound.remaining_capacity() > 0) and (not _inbound.error()); }, 39 | [&] { _outbound.end_input(); }); 40 | 41 | // rule 2: read from outbound byte stream into socket 42 | _eventloop.add_rule( 43 | socket, 44 | Direction::Out, 45 | [&] { 46 | const size_t bytes_to_write = min(max_copy_length, _outbound.buffer_size()); 47 | const size_t bytes_written = socket.write(_outbound.peek_output(bytes_to_write), false); 48 | _outbound.pop_output(bytes_written); 49 | if (_outbound.eof()) { 50 | socket.shutdown(SHUT_WR); 51 | _outbound_shutdown = true; 52 | } 53 | }, 54 | [&] { return (not _outbound.buffer_empty()) or (_outbound.eof() and not _outbound_shutdown); }, 55 | [&] { _outbound.end_input(); }); 56 | 57 | // rule 3: read from socket into inbound byte stream 58 | _eventloop.add_rule( 59 | socket, 60 | Direction::In, 61 | [&] { 62 | _inbound.write(socket.read(_inbound.remaining_capacity())); 63 | if (socket.eof()) { 64 | _inbound.end_input(); 65 | } 66 | }, 67 | [&] { return (not _inbound.error()) and (_inbound.remaining_capacity() > 0) and (not _outbound.error()); }, 68 | [&] { _inbound.end_input(); }); 69 | 70 | // rule 4: read from inbound byte stream into stdout 71 | _eventloop.add_rule( 72 | _output, 73 | Direction::Out, 74 | [&] { 75 | const size_t bytes_to_write = min(max_copy_length, _inbound.buffer_size()); 76 | const size_t bytes_written = _output.write(_inbound.peek_output(bytes_to_write), false); 77 | _inbound.pop_output(bytes_written); 78 | 79 | if (_inbound.eof()) { 80 | _output.close(); 81 | _inbound_shutdown = true; 82 | } 83 | }, 84 | [&] { return (not _inbound.buffer_empty()) or (_inbound.eof() and not _inbound_shutdown); }, 85 | [&] { _inbound.end_input(); }); 86 | 87 | // loop until completion 88 | while (true) { 89 | if (EventLoop::Result::Exit == _eventloop.wait_next_event(-1)) { 90 | return; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /apps/bidirectional_stream_copy.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_APPS_BIDIRECTIONAL_STREAM_COPY_HH 2 | #define SPONGE_APPS_BIDIRECTIONAL_STREAM_COPY_HH 3 | 4 | #include "socket.hh" 5 | 6 | //! Copy socket input/output to stdin/stdout until finished 7 | void bidirectional_stream_copy(Socket &socket); 8 | 9 | #endif // SPONGE_APPS_BIDIRECTIONAL_STREAM_COPY_HH 10 | -------------------------------------------------------------------------------- /apps/bouncer.cc: -------------------------------------------------------------------------------- 1 | #include "eventloop.hh" 2 | #include "socket.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | void program_body() { 11 | EventLoop loop; 12 | vector sockets; 13 | vector> peers; 14 | sockets.reserve(66000); 15 | peers.reserve(66000); 16 | 17 | for (uint16_t lower_port = 1024; lower_port <= 64000; lower_port += 2) { 18 | sockets.emplace_back(); 19 | sockets.emplace_back(); 20 | peers.emplace_back(); 21 | peers.emplace_back(); 22 | UDPSocket &x = sockets.at(sockets.size() - 2); 23 | UDPSocket &y = sockets.at(sockets.size() - 1); 24 | optional
&x_peer = peers.at(peers.size() - 2); 25 | optional
&y_peer = peers.at(peers.size() - 1); 26 | 27 | x.bind(Address{"0", lower_port}); 28 | y.bind(Address{"0", uint16_t(lower_port + 1)}); 29 | 30 | loop.add_rule(x, Direction::In, [&] { 31 | auto rec = x.recv(); 32 | if (not x_peer.has_value() or x_peer.value() != rec.source_address) { 33 | x_peer = rec.source_address; 34 | cerr << "Learned new address for X ( " << x.local_address().to_string() << " at " 35 | << x_peer.value().to_string() << "\n"; 36 | } 37 | if (y_peer.has_value() and not rec.payload.empty()) { 38 | y.sendto(y_peer.value(), rec.payload); 39 | } 40 | }); 41 | 42 | loop.add_rule(y, Direction::In, [&] { 43 | auto rec = y.recv(); 44 | if (not y_peer.has_value() or y_peer.value() != rec.source_address) { 45 | y_peer = rec.source_address; 46 | cerr << "Learned new address for Y ( " << y.local_address().to_string() << " at " 47 | << y_peer.value().to_string() << "\n"; 48 | } 49 | if (x_peer.has_value() and not rec.payload.empty()) { 50 | x.sendto(x_peer.value(), rec.payload); 51 | } 52 | }); 53 | } 54 | 55 | cerr << "Starting event loop...\n"; 56 | 57 | while (EventLoop::Result::Exit != loop.wait_next_event(-1)) { 58 | } 59 | } 60 | 61 | int main() { 62 | try { 63 | program_body(); 64 | } catch (const exception &e) { 65 | cerr << e.what() << "\n"; 66 | return EXIT_FAILURE; 67 | } 68 | 69 | return EXIT_SUCCESS; 70 | } 71 | -------------------------------------------------------------------------------- /apps/tcp_benchmark.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_connection.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | using namespace std::chrono; 11 | 12 | constexpr size_t len = 100 * 1024 * 1024; 13 | 14 | void move_segments(TCPConnection &x, TCPConnection &y, vector &segments, const bool reorder) { 15 | while (not x.segments_out().empty()) { 16 | segments.emplace_back(move(x.segments_out().front())); 17 | x.segments_out().pop(); 18 | } 19 | if (reorder) { 20 | for (auto it = segments.rbegin(); it != segments.rend(); ++it) { 21 | y.segment_received(move(*it)); 22 | } 23 | } else { 24 | for (auto it = segments.begin(); it != segments.end(); ++it) { 25 | y.segment_received(move(*it)); 26 | } 27 | } 28 | segments.clear(); 29 | } 30 | 31 | void main_loop(const bool reorder) { 32 | TCPConfig config; 33 | TCPConnection x{config}, y{config}; 34 | 35 | string string_to_send(len, 'x'); 36 | for (auto &ch : string_to_send) { 37 | ch = rand(); 38 | } 39 | 40 | Buffer bytes_to_send{string(string_to_send)}; 41 | x.connect(); 42 | y.end_input_stream(); 43 | 44 | bool x_closed = false; 45 | 46 | string string_received; 47 | string_received.reserve(len); 48 | 49 | const auto first_time = high_resolution_clock::now(); 50 | 51 | auto loop = [&] { 52 | // write input into x 53 | while (bytes_to_send.size() and x.remaining_outbound_capacity()) { 54 | const auto want = min(x.remaining_outbound_capacity(), bytes_to_send.size()); 55 | const auto written = x.write(string(bytes_to_send.str().substr(0, want))); 56 | if (want != written) { 57 | throw runtime_error("want = " + to_string(want) + ", written = " + to_string(written)); 58 | } 59 | bytes_to_send.remove_prefix(written); 60 | } 61 | 62 | if (bytes_to_send.size() == 0 and not x_closed) { 63 | x.end_input_stream(); 64 | x_closed = true; 65 | } 66 | 67 | // exchange segments between x and y but in reverse order 68 | vector segments; 69 | move_segments(x, y, segments, reorder); 70 | move_segments(y, x, segments, false); 71 | 72 | // read output from y 73 | const auto available_output = y.inbound_stream().buffer_size(); 74 | if (available_output > 0) { 75 | string_received.append(y.inbound_stream().read(available_output)); 76 | } 77 | 78 | // time passes 79 | x.tick(1000); 80 | y.tick(1000); 81 | }; 82 | 83 | while (not y.inbound_stream().eof()) { 84 | loop(); 85 | } 86 | 87 | if (string_received != string_to_send) { 88 | throw runtime_error("strings sent vs. received don't match"); 89 | } 90 | 91 | const auto final_time = high_resolution_clock::now(); 92 | 93 | const auto duration = duration_cast(final_time - first_time).count(); 94 | 95 | const auto gigabits_per_second = len * 8.0 / double(duration); 96 | 97 | cout << fixed << setprecision(2); 98 | cout << "CPU-limited throughput" << (reorder ? " with reordering: " : " : ") << gigabits_per_second 99 | << " Gbit/s\n"; 100 | 101 | while (x.active() or y.active()) { 102 | loop(); 103 | } 104 | } 105 | 106 | int main() { 107 | try { 108 | main_loop(false); 109 | main_loop(true); 110 | } catch (const exception &e) { 111 | cerr << e.what() << "\n"; 112 | return EXIT_FAILURE; 113 | } 114 | 115 | return EXIT_SUCCESS; 116 | } 117 | -------------------------------------------------------------------------------- /apps/tcp_native.cc: -------------------------------------------------------------------------------- 1 | #include "bidirectional_stream_copy.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | void show_usage(const char *argv0) { 10 | cerr << "Usage: " << argv0 << " [-l] \n\n" 11 | << " -l specifies listen mode; : is the listening address." << endl; 12 | } 13 | 14 | int main(int argc, char **argv) { 15 | try { 16 | bool server_mode = false; 17 | if (argc < 3 || ((server_mode = (strncmp("-l", argv[1], 3) == 0)) && argc < 4)) { 18 | show_usage(argv[0]); 19 | return EXIT_FAILURE; 20 | } 21 | 22 | // in client mode, connect; in server mode, accept exactly one connection 23 | auto socket = [&] { 24 | if (server_mode) { 25 | TCPSocket listening_socket; // create a TCP socket 26 | listening_socket.set_reuseaddr(); // reuse the server's address as soon as the program quits 27 | listening_socket.bind({argv[2], argv[3]}); // bind to specified address 28 | listening_socket.listen(); // mark the socket as listening for incoming connections 29 | return listening_socket.accept(); // accept exactly one connection 30 | } 31 | TCPSocket connecting_socket; 32 | connecting_socket.connect({argv[1], argv[2]}); 33 | return connecting_socket; 34 | }(); 35 | 36 | bidirectional_stream_copy(socket); 37 | } catch (const exception &e) { 38 | cerr << "Exception: " << e.what() << endl; 39 | return EXIT_FAILURE; 40 | } 41 | 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /apps/tun.cc: -------------------------------------------------------------------------------- 1 | #include "tun.hh" 2 | 3 | #include "ipv4_datagram.hh" 4 | #include "parser.hh" 5 | #include "tcp_segment.hh" 6 | #include "util.hh" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace std; 14 | 15 | int main() { 16 | try { 17 | TunFD tun("tun144"); 18 | while (true) { 19 | auto buffer = tun.read(); 20 | cout << "\n\n***\n*** Got packet:\n***\n"; 21 | hexdump(buffer.data(), buffer.size()); 22 | 23 | IPv4Datagram ip_dgram; 24 | 25 | cout << "attempting to parse as ipv4 datagram... "; 26 | if (ip_dgram.parse(move(buffer)) != ParseResult::NoError) { 27 | cout << "failed.\n"; 28 | continue; 29 | } 30 | 31 | cout << "success! totlen=" << ip_dgram.header().len << ", IPv4 header contents:\n"; 32 | cout << ip_dgram.header().to_string(); 33 | 34 | if (ip_dgram.header().proto != IPv4Header::PROTO_TCP) { 35 | cout << "\nNot TCP, skipping.\n"; 36 | continue; 37 | } 38 | 39 | cout << "\nAttempting to parse as a TCP segment... "; 40 | 41 | TCPSegment tcp_seg; 42 | 43 | if (tcp_seg.parse(ip_dgram.payload(), ip_dgram.header().pseudo_cksum()) != ParseResult::NoError) { 44 | cout << "failed.\n"; 45 | continue; 46 | } 47 | 48 | cout << "success! payload len=" << tcp_seg.payload().size() << ", TCP header contents:\n"; 49 | cout << tcp_seg.header().to_string() << endl; 50 | } 51 | } catch (const exception &e) { 52 | cout << "Exception: " << e.what() << endl; 53 | return EXIT_FAILURE; 54 | } 55 | 56 | return EXIT_SUCCESS; 57 | } 58 | -------------------------------------------------------------------------------- /apps/webget.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_sponge_socket.hh" 2 | #include "util.hh" 3 | 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | void get_URL(const string &host, const string &path) { 10 | // Your code here. 11 | 12 | // You will need to connect to the "http" service on 13 | // the computer whose name is in the "host" string, 14 | // then request the URL path given in the "path" string. 15 | 16 | // Then you'll need to print out everything the server sends back, 17 | // (not just one call to read() -- everything) until you reach 18 | // the "eof" (end of file). 19 | 20 | Address addr(host, "http"); 21 | FullStackSocket http_tcp; 22 | http_tcp.connect(addr); 23 | http_tcp.write("GET " + path + " HTTP/1.1\r\n"); 24 | http_tcp.write("HOST: " + host + "\r\n"); 25 | http_tcp.write("Connection: close\r\n"); 26 | http_tcp.write("\r\n"); 27 | 28 | while (!http_tcp.eof()) 29 | cout << http_tcp.read(); 30 | http_tcp.wait_until_closed(); 31 | } 32 | 33 | int main(int argc, char *argv[]) { 34 | try { 35 | if (argc <= 0) { 36 | abort(); // For sticklers: don't try to access argv[0] if argc <= 0. 37 | } 38 | 39 | // The program takes two command-line arguments: the hostname and "path" part of the URL. 40 | // Print the usage message unless there are these two arguments (plus the program name 41 | // itself, so arg count = 3 in total). 42 | if (argc != 3) { 43 | cerr << "Usage: " << argv[0] << " HOST PATH\n"; 44 | cerr << "\tExample: " << argv[0] << " stanford.edu /class/cs144\n"; 45 | return EXIT_FAILURE; 46 | } 47 | 48 | // Get the command-line arguments. 49 | const string host = argv[1]; 50 | const string path = argv[2]; 51 | 52 | // Call the student-written function. 53 | get_URL(host, path); 54 | } catch (const exception &e) { 55 | cerr << e.what() << "\n"; 56 | return EXIT_FAILURE; 57 | } 58 | 59 | return EXIT_SUCCESS; 60 | } 61 | -------------------------------------------------------------------------------- /compile_commands.json: -------------------------------------------------------------------------------- 1 | build/compile_commands.json -------------------------------------------------------------------------------- /doctests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_sponge_exec (address_dt) 2 | add_sponge_exec (parser_dt) 3 | add_sponge_exec (socket_dt) 4 | -------------------------------------------------------------------------------- /doctests/address_dt.cc: -------------------------------------------------------------------------------- 1 | #include "address.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int main() { 8 | try { 9 | #include "address_example_1.cc" 10 | #include "address_example_2.cc" 11 | #include "address_example_3.cc" 12 | if ((google_webserver.port() != 443) || (a_dns_server_numeric != 0x12'47'00'97)) { 13 | throw std::runtime_error("unexpected value"); 14 | } 15 | } catch (const std::exception &e) { 16 | std::cerr << "This test requires Internet access and working DNS.\n"; 17 | std::cerr << "Error: " << e.what() << "\n"; 18 | return EXIT_FAILURE; 19 | } 20 | return EXIT_SUCCESS; 21 | } 22 | -------------------------------------------------------------------------------- /doctests/address_example_1.cc: -------------------------------------------------------------------------------- 1 | const Address google_webserver("www.google.com", "https"); 2 | -------------------------------------------------------------------------------- /doctests/address_example_2.cc: -------------------------------------------------------------------------------- 1 | const Address a_dns_server("18.71.0.151", 53); 2 | -------------------------------------------------------------------------------- /doctests/address_example_3.cc: -------------------------------------------------------------------------------- 1 | const uint32_t a_dns_server_numeric = a_dns_server.ipv4_numeric(); 2 | -------------------------------------------------------------------------------- /doctests/parser_dt.cc: -------------------------------------------------------------------------------- 1 | #include "parser.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main() { 9 | try { 10 | #include "parser_example.cc" 11 | } catch (...) { 12 | return EXIT_FAILURE; 13 | } 14 | return EXIT_SUCCESS; 15 | } 16 | -------------------------------------------------------------------------------- /doctests/parser_example.cc: -------------------------------------------------------------------------------- 1 | const uint32_t val1 = 0xdeadbeef; 2 | const uint16_t val2 = 0xc0c0; 3 | const uint8_t val3 = 0xff; 4 | const uint32_t val4 = 0x0c05fefe; 5 | 6 | // first, let's serialize it 7 | std::string buffer; 8 | buffer.push_back(0x32); // manually added to beginning of string 9 | { 10 | NetUnparser p; 11 | p.u32(buffer, val1); 12 | p.u16(buffer, val2); 13 | p.u8(buffer, val3); 14 | p.u32(buffer, val4); 15 | } // p goes out of scope, data is in buffer 16 | 17 | // now let's deserialize it 18 | uint8_t out0, out3; 19 | uint32_t out1, out4; 20 | uint16_t out2; 21 | { 22 | NetParser p{std::string(buffer)}; // NOTE: starting at offset 0 23 | out0 = p.u8(); // buffer[0], which we manually set to 0x32 above 24 | out1 = p.u32(); // parse out val1 25 | out2 = p.u16(); // val2 26 | out3 = p.u8(); // val3 27 | out4 = p.u32(); // val4 28 | } // p goes out of scope 29 | 30 | if (out0 != 0x32 || out1 != val1 || out2 != val2 || out3 != val3 || out4 != val4) { 31 | throw std::runtime_error("bad parse"); 32 | } 33 | -------------------------------------------------------------------------------- /doctests/socket_dt.cc: -------------------------------------------------------------------------------- 1 | #include "socket.hh" 2 | 3 | #include "address.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | int main() { 14 | try { 15 | { 16 | #include "socket_example_1.cc" 17 | } { 18 | #include "socket_example_2.cc" 19 | } { 20 | #include "socket_example_3.cc" 21 | } 22 | } catch (...) { 23 | return EXIT_FAILURE; 24 | } 25 | return EXIT_SUCCESS; 26 | } 27 | -------------------------------------------------------------------------------- /doctests/socket_example_1.cc: -------------------------------------------------------------------------------- 1 | const uint16_t portnum = ((std::random_device()()) % 50000) + 1025; 2 | 3 | // create a UDP socket and bind it to a local address 4 | UDPSocket sock1; 5 | sock1.bind(Address("127.0.0.1", portnum)); 6 | 7 | // create another UDP socket and send a datagram to the first socket without connecting 8 | UDPSocket sock2; 9 | sock2.sendto(Address("127.0.0.1", portnum), "hi there"); 10 | 11 | // receive sent datagram, connect the socket to the peer's address, and send a response 12 | auto recvd = sock1.recv(); 13 | sock1.connect(recvd.source_address); 14 | sock1.send("hi yourself"); 15 | 16 | auto recvd2 = sock2.recv(); 17 | 18 | if (recvd.payload != "hi there" || recvd2.payload != "hi yourself") { 19 | throw std::runtime_error("wrong data received"); 20 | } 21 | -------------------------------------------------------------------------------- /doctests/socket_example_2.cc: -------------------------------------------------------------------------------- 1 | const uint16_t portnum = ((std::random_device()()) % 50000) + 1025; 2 | 3 | // create a TCP socket, bind it to a local address, and listen 4 | TCPSocket sock1; 5 | sock1.bind(Address("127.0.0.1", portnum)); 6 | sock1.listen(1); 7 | 8 | // create another socket and connect to the first one 9 | TCPSocket sock2; 10 | sock2.connect(Address("127.0.0.1", portnum)); 11 | 12 | // accept the connection 13 | auto sock3 = sock1.accept(); 14 | sock3.write("hi there"); 15 | 16 | auto recvd = sock2.read(); 17 | sock2.write("hi yourself"); 18 | 19 | auto recvd2 = sock3.read(); 20 | 21 | sock1.close(); // don't need to accept any more connections 22 | sock2.close(); // you can call close(2) on a socket 23 | sock3.shutdown(SHUT_RDWR); // you can also shutdown(2) a socket 24 | if (recvd != "hi there" || recvd2 != "hi yourself") { 25 | throw std::runtime_error("wrong data received"); 26 | } 27 | -------------------------------------------------------------------------------- /doctests/socket_example_3.cc: -------------------------------------------------------------------------------- 1 | // create a pair of stream sockets 2 | std::array fds{}; 3 | SystemCall("socketpair", ::socketpair(AF_UNIX, SOCK_STREAM, 0, fds.data())); 4 | LocalStreamSocket pipe1{FileDescriptor(fds[0])}, pipe2{FileDescriptor(fds[1])}; 5 | 6 | pipe1.write("hi there"); 7 | auto recvd = pipe2.read(); 8 | 9 | pipe2.write("hi yourself"); 10 | auto recvd2 = pipe1.read(); 11 | 12 | if (recvd != "hi there" || recvd2 != "hi yourself") { 13 | throw std::runtime_error("wrong data received"); 14 | } 15 | -------------------------------------------------------------------------------- /etc/Doxyfile.in: -------------------------------------------------------------------------------- 1 | # Doxyfile 1.8.14 2 | 3 | DOXYFILE_ENCODING = UTF-8 4 | PROJECT_NAME = "Sponge" 5 | PROJECT_BRIEF = "CS144's user-space TCP library" 6 | PROJECT_LOGO = "@PROJECT_SOURCE_DIR@/etc/sponge_small.png" 7 | INPUT = @PROJECT_SOURCE_DIR@ 8 | RECURSIVE = YES 9 | EXCLUDE = @PROJECT_SOURCE_DIR@/etc @PROJECT_SOURCE_DIR@/build @PROJECT_SOURCE_DIR@/tests @PROJECT_SOURCE_DIR@/writeups 10 | OUTPUT_DIRECTORY = "@PROJECT_BINARY_DIR@/doc" 11 | CASE_SENSE_NAMES = NO 12 | SORT_BRIEF_DOCS = YES 13 | SORT_MEMBERS_CTORS_1ST = YES 14 | SHOW_NAMESPACES = NO 15 | USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md 16 | SOURCE_BROWSER = YES 17 | EXT_LINKS_IN_WINDOW = YES 18 | INCLUDE_PATH = @PROJECT_SOURCE_DIR@/libsponge 19 | TAGFILES = "@PROJECT_SOURCE_DIR@/etc/cppreference-doxygen-web.tag.xml=https://en.cppreference.com/w/" 20 | TAGFILES += "@PROJECT_SOURCE_DIR@/etc/linux-man-doxygen-web.tag.xml=https://man7.org/linux/man-pages/" 21 | TAGFILES += "@PROJECT_SOURCE_DIR@/etc/rfc-doxygen-web.tag.xml=https://tools.ietf.org/html/" 22 | HIDE_UNDOC_RELATIONS = NO 23 | INLINE_GROUPED_CLASSES = YES 24 | INLINE_SIMPLE_STRUCTS = YES 25 | HTML_COLORSTYLE_HUE = 204 26 | HTML_COLORSTYLE_SAT = 120 27 | HTML_COLORSTYLE_GAMMA = 60 28 | HTML_EXTRA_STYLESHEET = "@PROJECT_SOURCE_DIR@/etc/sponge_doxygen.css" 29 | GENERATE_LATEX = NO 30 | EXAMPLE_PATH = "@PROJECT_SOURCE_DIR@/doctests" 31 | 32 | # cmake detects whether dot is available 33 | HAVE_DOT = @DOXYGEN_DOT_FOUND@ 34 | CLASS_GRAPH = YES 35 | TEMPLATE_RELATIONS = YES 36 | DOT_IMAGE_FORMAT = png 37 | INTERACTIVE_SVG = NO 38 | COLLABORATION_GRAPH = NO 39 | 40 | # ??? temporary 41 | EXTRACT_ALL = YES 42 | EXTRACT_PRIVATE = YES 43 | EXTRACT_STATIC = YES 44 | EXTRACT_ANON_NSPACES = YES 45 | 46 | # do u liek eclips 47 | GENERATE_ECLIPSEHELP = NO 48 | ECLIPSE_DOC_ID = edu.stanford.cs144.sponge 49 | -------------------------------------------------------------------------------- /etc/build_defs.cmake: -------------------------------------------------------------------------------- 1 | find_library (LIBPCAP pcap) 2 | find_library (LIBPTHREAD pthread) 3 | macro (add_sponge_exec exec_name) 4 | add_executable ("${exec_name}" "${exec_name}.cc") 5 | target_link_libraries ("${exec_name}" ${ARGN} sponge ${LIBPTHREAD}) 6 | endmacro (add_sponge_exec) 7 | -------------------------------------------------------------------------------- /etc/build_type.cmake: -------------------------------------------------------------------------------- 1 | set (default_build_type "Release") 2 | if (NOT (CMAKE_BUILD_TYPE_SHADOW STREQUAL CMAKE_BUILD_TYPE)) 3 | if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 4 | message (STATUS "Setting build type to '${default_build_type}'") 5 | set (CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) 6 | else () 7 | message (STATUS "Building in ${CMAKE_BUILD_TYPE} mode as requested.") 8 | endif () 9 | set (CMAKE_BUILD_TYPE_SHADOW ${CMAKE_BUILD_TYPE} CACHE STRING "used to detect changes in build type" FORCE) 10 | endif () 11 | 12 | message (STATUS " NOTE: You can choose a build type by calling cmake with one of:") 13 | message (STATUS " -DCMAKE_BUILD_TYPE=Release -- full optimizations") 14 | message (STATUS " -DCMAKE_BUILD_TYPE=Debug -- better debugging experience in gdb") 15 | message (STATUS " -DCMAKE_BUILD_TYPE=RelASan -- full optimizations plus address and undefined-behavior sanitizers") 16 | message (STATUS " -DCMAKE_BUILD_TYPE=DebugASan -- debug plus sanitizers") 17 | -------------------------------------------------------------------------------- /etc/cflags.cmake: -------------------------------------------------------------------------------- 1 | set (CMAKE_CXX_STANDARD 17) 2 | set (CMAKE_EXPORT_COMPILE_COMMANDS ON) 3 | 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") 4 | 5 | # check for supported compiler versions 6 | set (IS_GNU_COMPILER ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")) 7 | set (IS_CLANG_COMPILER ("${CMAKE_CXX_COMPILER_ID}" MATCHES "[Cc][Ll][Aa][Nn][Gg]")) 8 | set (CXX_VERSION_LT_6 ("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 6)) 9 | set (CXX_VERSION_LT_8 ("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 8)) 10 | if ((${IS_GNU_COMPILER} AND ${CXX_VERSION_LT_8}) OR (${IS_CLANG_COMPILER} AND ${CXX_VERSION_LT_6})) 11 | message (FATAL_ERROR "You must compile this project with g++ >= 8 or clang >= 6.") 12 | endif () 13 | if (${IS_CLANG_COMPILER}) 14 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wloop-analysis") 15 | endif () 16 | 17 | # add some flags for the Release, Debug, and DebugSan modes 18 | set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb3 -g3 -O0") 19 | set (CMAKE_CXX_FLAGS_DEBUGASAN "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=undefined -fsanitize=address") 20 | set (CMAKE_CXX_FLAGS_RELASAN "${CMAKE_CXX_FLAGS_RELEASE} -fsanitize=undefined -fsanitize=address") 21 | -------------------------------------------------------------------------------- /etc/clang_format.cmake: -------------------------------------------------------------------------------- 1 | if (NOT CLANG_FORMAT) 2 | if (DEFINED ENV{CLANG_FORMAT}) 3 | set (CLANG_FORMAT_TMP $ENV{CLANG_FORMAT}) 4 | else (NOT DEFINED ENV{CLANG_FORMAT}) 5 | set (CLANG_FORMAT_TMP clang-format-6.0) 6 | endif (DEFINED ENV{CLANG_FORMAT}) 7 | 8 | # figure out which version of clang-format we're using 9 | execute_process (COMMAND ${CLANG_FORMAT_TMP} --version RESULT_VARIABLE CLANG_FORMAT_RESULT OUTPUT_VARIABLE CLANG_FORMAT_VERSION) 10 | if (${CLANG_FORMAT_RESULT} EQUAL 0) 11 | string (REGEX MATCH "version [0-9]" CLANG_FORMAT_VERSION ${CLANG_FORMAT_VERSION}) 12 | message (STATUS "Found clang-format " ${CLANG_FORMAT_VERSION}) 13 | set(CLANG_FORMAT ${CLANG_FORMAT_TMP} CACHE STRING "clang-format executable name") 14 | endif (${CLANG_FORMAT_RESULT} EQUAL 0) 15 | endif (NOT CLANG_FORMAT) 16 | 17 | if (DEFINED CLANG_FORMAT) 18 | file (GLOB_RECURSE ALL_CC_FILES *.cc) 19 | file (GLOB_RECURSE ALL_HH_FILES *.hh) 20 | add_custom_target (format ${CLANG_FORMAT} -i ${ALL_CC_FILES} ${ALL_HH_FILES} COMMENT "Formatted all source files.") 21 | else (NOT DEFINED CLANG_FORMAT) 22 | add_custom_target (format echo "Could not find clang-format. Please install and re-run cmake") 23 | endif (DEFINED CLANG_FORMAT) 24 | -------------------------------------------------------------------------------- /etc/clang_tidy.cmake: -------------------------------------------------------------------------------- 1 | if (NOT CLANG_TIDY) 2 | if (DEFINED ENV{CLANG_TIDY}) 3 | set (CLANG_TIDY_TMP $ENV{CLANG_TIDY}) 4 | else (NOT DEFINED ENV{CLANG_TIDY}) 5 | set (CLANG_TIDY_TMP clang-tidy) 6 | endif (DEFINED ENV{CLANG_TIDY}) 7 | 8 | # is clang-tidy available? 9 | execute_process (COMMAND ${CLANG_TIDY_TMP} --version RESULT_VARIABLE CLANG_TIDY_RESULT OUTPUT_VARIABLE CLANG_TIDY_VERSION) 10 | if (${CLANG_TIDY_RESULT} EQUAL 0) 11 | string (REGEX MATCH "version [0-9]" CLANG_TIDY_VERSION ${CLANG_TIDY_VERSION}) 12 | message (STATUS "Found clang-tidy " ${CLANG_TIDY_VERSION}) 13 | set (CLANG_TIDY ${CLANG_TIDY_TMP} CACHE STRING "clang-tidy executable name") 14 | endif (${CLANG_TIDY_RESULT} EQUAL 0) 15 | endif (NOT CLANG_TIDY) 16 | 17 | if (DEFINED CLANG_TIDY) 18 | file (GLOB_RECURSE ALL_CC_FILES *.cc) 19 | 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'") 20 | foreach (tidy_target ${ALL_CC_FILES}) 21 | get_filename_component (basename ${tidy_target} NAME) 22 | get_filename_component (dirname ${tidy_target} DIRECTORY) 23 | get_filename_component (basedir ${dirname} NAME) 24 | set (tidy_target_name "${basedir}__${basename}") 25 | set (tidy_command ${CLANG_TIDY} -checks=${CLANG_TIDY_CHECKS} -header-filter=.* -p=${PROJECT_BINARY_DIR} ${tidy_target}) 26 | add_custom_target (tidy_quiet_${tidy_target_name} ${tidy_command} 2>/dev/null) 27 | add_custom_target (tidy_${tidy_target_name} ${tidy_command}) 28 | list (APPEND ALL_TIDY_TARGETS tidy_quiet_${tidy_target_name}) 29 | list (APPEND ALL_TIDY_VERBOSE_TARGETS tidy_${tidy_target_name}) 30 | endforeach (tidy_target) 31 | add_custom_target (tidy DEPENDS ${ALL_TIDY_TARGETS}) 32 | add_custom_target (tidy_verbose DEPENDS ${ALL_TIDY_VERBOSE_TARGETS}) 33 | endif (DEFINED CLANG_TIDY) 34 | -------------------------------------------------------------------------------- /etc/cppcheck.cmake: -------------------------------------------------------------------------------- 1 | if (NOT CPPCHECK) 2 | if (DEFINED ENV{CPPCHECK}) 3 | set (CPPCHECK_TMP $ENV{CPPCHECK}) 4 | else (NOT DEFINED ENV{CPPCHECK}) 5 | set (CPPCHECK_TMP cppcheck) 6 | endif () 7 | 8 | # is cppcheck available? 9 | execute_process (COMMAND ${CPPCHECK_TMP} --version RESULT_VARIABLE CPPCHECK_RESULT OUTPUT_VARIABLE CPPCHECK_OUTPUT) 10 | if (${CPPCHECK_RESULT} EQUAL 0) 11 | message (STATUS "Found cppcheck") 12 | set (CPPCHECK ${CPPCHECK_TMP} CACHE STRING "cppcheck executable name") 13 | endif() 14 | endif (NOT CPPCHECK) 15 | 16 | if (DEFINED CPPCHECK) 17 | add_custom_target (cppcheck ${CPPCHECK} --enable=all --project="${PROJECT_BINARY_DIR}/compile_commands.json") 18 | endif (DEFINED CPPCHECK) 19 | -------------------------------------------------------------------------------- /etc/doxygen.cmake: -------------------------------------------------------------------------------- 1 | find_package (Doxygen) 2 | if (DOXYGEN_FOUND) 3 | if (Doxygen_dot_FOUND) 4 | set (DOXYGEN_DOT_FOUND YES) 5 | else (NOT Doxygen_dot_FOUND) 6 | set (DOXYGEN_DOT_FOUND NO) 7 | endif (Doxygen_dot_FOUND) 8 | configure_file ("${PROJECT_SOURCE_DIR}/etc/Doxyfile.in" "${PROJECT_BINARY_DIR}/Doxyfile" @ONLY) 9 | add_custom_target (doc "${DOXYGEN_EXECUTABLE}" "${PROJECT_BINARY_DIR}/Doxyfile" 10 | WORKING_DIRECTORY "${PROJECT_BINARY_DIR}" 11 | COMMENT "Generate docs using Doxygen" VERBATIM) 12 | endif () 13 | -------------------------------------------------------------------------------- /etc/rfc-doxygen-web.tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | rfc 4 | 5 | 6 | rfc791 7 | rfc791 8 | 9 | 10 | 11 | 12 | 13 | rfc793 14 | rfc793 15 | 16 | 17 | 18 | 19 | 20 | rfc826 21 | rfc826 22 | 23 | 24 | 25 | 26 | 27 | rfc6298 28 | rfc6298 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /etc/sponge_doxygen.css: -------------------------------------------------------------------------------- 1 | html, body { background-color: #F8F8F8; } 2 | div.textblock>p,div.memdoc>p,dl.section.note>dd { max-width: 750px; } 3 | div.line,pre.fragment { line-height: 1.5; } 4 | div.contents { 5 | padding: 12px; 6 | margin-top: auto; 7 | margin-bottom: auto; 8 | margin-left: 3%; 9 | margin-right: 6%; 10 | border-radius: 8px; 11 | } 12 | -------------------------------------------------------------------------------- /etc/sponge_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kiprey/sponge/33e6ba5ebe92f94fc0a177c2debacb3a04b6e300/etc/sponge_small.png -------------------------------------------------------------------------------- /etc/tunconfig: -------------------------------------------------------------------------------- 1 | TUN_IP_PREFIX=169.254 2 | -------------------------------------------------------------------------------- /labs_pdf/lab0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kiprey/sponge/33e6ba5ebe92f94fc0a177c2debacb3a04b6e300/labs_pdf/lab0.pdf -------------------------------------------------------------------------------- /labs_pdf/lab1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kiprey/sponge/33e6ba5ebe92f94fc0a177c2debacb3a04b6e300/labs_pdf/lab1.pdf -------------------------------------------------------------------------------- /labs_pdf/lab2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kiprey/sponge/33e6ba5ebe92f94fc0a177c2debacb3a04b6e300/labs_pdf/lab2.pdf -------------------------------------------------------------------------------- /labs_pdf/lab3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kiprey/sponge/33e6ba5ebe92f94fc0a177c2debacb3a04b6e300/labs_pdf/lab3.pdf -------------------------------------------------------------------------------- /labs_pdf/lab4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kiprey/sponge/33e6ba5ebe92f94fc0a177c2debacb3a04b6e300/labs_pdf/lab4.pdf -------------------------------------------------------------------------------- /labs_pdf/lab5.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kiprey/sponge/33e6ba5ebe92f94fc0a177c2debacb3a04b6e300/labs_pdf/lab5.pdf -------------------------------------------------------------------------------- /labs_pdf/lab6.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kiprey/sponge/33e6ba5ebe92f94fc0a177c2debacb3a04b6e300/labs_pdf/lab6.pdf -------------------------------------------------------------------------------- /labs_pdf/lab7.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kiprey/sponge/33e6ba5ebe92f94fc0a177c2debacb3a04b6e300/labs_pdf/lab7.pdf -------------------------------------------------------------------------------- /libsponge/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file (GLOB LIB_SOURCES "*.cc" "util/*.cc" "tcp_helpers/*.cc") 2 | add_library (sponge STATIC ${LIB_SOURCES}) 3 | -------------------------------------------------------------------------------- /libsponge/byte_stream.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | 3 | // Dummy implementation of a flow-controlled in-memory byte stream. 4 | 5 | // For Lab 0, please replace with a real implementation that passes the 6 | // automated checks run by `make check_lab0`. 7 | 8 | // You will need to add private members to the class declaration in `byte_stream.hh` 9 | 10 | template 11 | void DUMMY_CODE(Targs &&.../* unused */) {} 12 | 13 | using namespace std; 14 | 15 | ByteStream::ByteStream(const size_t capacity) 16 | : _queue(), _capacity_size(capacity), _written_size(0), _read_size(0), _end_input(false), _error(false) {} 17 | 18 | size_t ByteStream::write(const string &data) { 19 | if (_end_input) 20 | return 0; 21 | size_t write_size = min(data.size(), _capacity_size - _queue.size()); 22 | _written_size += write_size; 23 | for (size_t i = 0; i < write_size; i++) 24 | _queue.push_back(data[i]); 25 | return write_size; 26 | } 27 | 28 | //! \param[in] len bytes will be copied from the output side of the buffer 29 | string ByteStream::peek_output(const size_t len) const { 30 | size_t pop_size = min(len, _queue.size()); 31 | return string(_queue.begin(), _queue.begin() + pop_size); 32 | } 33 | 34 | //! \param[in] len bytes will be removed from the output side of the buffer 35 | void ByteStream::pop_output(const size_t len) { 36 | size_t pop_size = min(len, _queue.size()); 37 | _read_size += len; 38 | for (size_t i = 0; i < pop_size; i++) 39 | _queue.pop_front(); 40 | } 41 | 42 | //! Read (i.e., copy and then pop) the next "len" bytes of the stream 43 | //! \param[in] len bytes will be popped and returned 44 | //! \returns a string 45 | std::string ByteStream::read(const size_t len) { 46 | string data = this->peek_output(len); 47 | this->pop_output(len); 48 | return data; 49 | } 50 | 51 | void ByteStream::end_input() { _end_input = true; } 52 | 53 | bool ByteStream::input_ended() const { return _end_input; } 54 | 55 | size_t ByteStream::buffer_size() const { return _queue.size(); } 56 | 57 | bool ByteStream::buffer_empty() const { return _queue.empty(); } 58 | 59 | bool ByteStream::eof() const { return _end_input && _queue.empty(); } 60 | 61 | size_t ByteStream::bytes_written() const { return _written_size; } 62 | 63 | size_t ByteStream::bytes_read() const { return _read_size; } 64 | 65 | size_t ByteStream::remaining_capacity() const { return _capacity_size - _queue.size(); } 66 | -------------------------------------------------------------------------------- /libsponge/byte_stream.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_BYTE_STREAM_HH 2 | #define SPONGE_LIBSPONGE_BYTE_STREAM_HH 3 | 4 | #include 5 | #include 6 | 7 | //! \brief An in-order byte stream. 8 | 9 | //! Bytes are written on the "input" side and read from the "output" 10 | //! side. The byte stream is finite: the writer can end the input, 11 | //! and then no more bytes can be written. 12 | class ByteStream { 13 | private: 14 | // Your code here -- add private members as necessary. 15 | 16 | // Hint: This doesn't need to be a sophisticated data structure at 17 | // all, but if any of your tests are taking longer than a second, 18 | // that's a sign that you probably want to keep exploring 19 | // different approaches. 20 | std::deque _queue; 21 | size_t _capacity_size; 22 | size_t _written_size; 23 | size_t _read_size; 24 | bool _end_input; 25 | bool _error{}; //!< Flag indicating that the stream suffered an error. 26 | 27 | public: 28 | //! Construct a stream with room for `capacity` bytes. 29 | ByteStream(const size_t capacity); 30 | 31 | //! \name "Input" interface for the writer 32 | //!@{ 33 | 34 | //! Write a string of bytes into the stream. Write as many 35 | //! as will fit, and return how many were written. 36 | //! \returns the number of bytes accepted into the stream 37 | size_t write(const std::string &data); 38 | 39 | //! \returns the number of additional bytes that the stream has space for 40 | size_t remaining_capacity() const; 41 | 42 | //! Signal that the byte stream has reached its ending 43 | void end_input(); 44 | 45 | //! Indicate that the stream suffered an error. 46 | void set_error() { _error = true; } 47 | //!@} 48 | 49 | //! \name "Output" interface for the reader 50 | //!@{ 51 | 52 | //! Peek at next "len" bytes of the stream 53 | //! \returns a string 54 | std::string peek_output(const size_t len) const; 55 | 56 | //! Remove bytes from the buffer 57 | void pop_output(const size_t len); 58 | 59 | //! Read (i.e., copy and then pop) the next "len" bytes of the stream 60 | //! \returns a string 61 | std::string read(const size_t len); 62 | 63 | //! \returns `true` if the stream input has ended 64 | bool input_ended() const; 65 | 66 | //! \returns `true` if the stream has suffered an error 67 | bool error() const { return _error; } 68 | 69 | //! \returns the maximum amount that can currently be read from the stream 70 | size_t buffer_size() const; 71 | 72 | //! \returns `true` if the buffer is empty 73 | bool buffer_empty() const; 74 | 75 | //! \returns `true` if the output has reached the ending 76 | bool eof() const; 77 | //!@} 78 | 79 | //! \name General accounting 80 | //!@{ 81 | 82 | //! Total number of bytes written 83 | size_t bytes_written() const; 84 | 85 | //! Total number of bytes popped 86 | size_t bytes_read() const; 87 | //!@} 88 | }; 89 | 90 | #endif // SPONGE_LIBSPONGE_BYTE_STREAM_HH 91 | -------------------------------------------------------------------------------- /libsponge/router.cc: -------------------------------------------------------------------------------- 1 | #include "router.hh" 2 | 3 | #include 4 | 5 | using namespace std; 6 | 7 | // Dummy implementation of an IP router 8 | 9 | // Given an incoming Internet datagram, the router decides 10 | // (1) which interface to send it out on, and 11 | // (2) what next hop address to send it to. 12 | 13 | // For Lab 6, please replace with a real implementation that passes the 14 | // automated checks run by `make check_lab6`. 15 | 16 | // You will need to add private members to the class declaration in `router.hh` 17 | 18 | template 19 | void DUMMY_CODE(Targs &&.../* unused */) {} 20 | 21 | //! \param[in] route_prefix The "up-to-32-bit" IPv4 address prefix to match the datagram's destination address against 22 | //! \param[in] prefix_length For this route to be applicable, how many high-order (most-significant) bits of the route_prefix will need to match the corresponding bits of the datagram's destination address? 23 | //! \param[in] next_hop The IP address of the next hop. Will be empty if the network is directly attached to the router (in which case, the next hop address should be the datagram's final destination). 24 | //! \param[in] interface_num The index of the interface to send the datagram out on. 25 | void Router::add_route(const uint32_t route_prefix, 26 | const uint8_t prefix_length, 27 | const optional
next_hop, 28 | const size_t interface_num) { 29 | cerr << "DEBUG: adding route " << Address::from_ipv4_numeric(route_prefix).ip() << "/" << int(prefix_length) 30 | << " => " << (next_hop.has_value() ? next_hop->ip() : "(direct)") << " on interface " << interface_num << "\n"; 31 | 32 | _router_table.push_back({route_prefix, prefix_length, next_hop, interface_num}); 33 | } 34 | 35 | //! \param[in] dgram The datagram to be routed 36 | void Router::route_one_datagram(InternetDatagram &dgram) { 37 | const uint32_t dst_ip_addr = dgram.header().dst; 38 | auto max_matched_entry = _router_table.end(); 39 | // 开始查询 40 | for (auto router_entry_iter = _router_table.begin(); router_entry_iter != _router_table.end(); 41 | router_entry_iter++) { 42 | // 如果前缀匹配匹配长度为 0,或者前缀匹配相同 43 | if (router_entry_iter->prefix_length == 0 || 44 | (router_entry_iter->route_prefix ^ dst_ip_addr) >> (32 - router_entry_iter->prefix_length) == 0) { 45 | // 如果条件符合,则更新最匹配的条目 46 | if (max_matched_entry == _router_table.end() || 47 | max_matched_entry->prefix_length < router_entry_iter->prefix_length) 48 | max_matched_entry = router_entry_iter; 49 | } 50 | } 51 | // 将数据包 TTL 减去1 52 | // 如果存在最匹配的,并且数据包仍然存活,则将其转发 53 | if (max_matched_entry != _router_table.end() && dgram.header().ttl-- > 1) { 54 | const optional
next_hop = max_matched_entry->next_hop; 55 | AsyncNetworkInterface &interface = _interfaces[max_matched_entry->interface_idx]; 56 | if (next_hop.has_value()) 57 | interface.send_datagram(dgram, next_hop.value()); 58 | else 59 | interface.send_datagram(dgram, Address::from_ipv4_numeric(dst_ip_addr)); 60 | } 61 | // 其他情况下则丢弃该数据包 62 | } 63 | 64 | void Router::route() { 65 | // Go through all the interfaces, and route every incoming datagram to its proper outgoing interface. 66 | for (auto &interface : _interfaces) { 67 | auto &queue = interface.datagrams_out(); 68 | while (not queue.empty()) { 69 | route_one_datagram(queue.front()); 70 | queue.pop(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /libsponge/router.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_ROUTER_HH 2 | #define SPONGE_LIBSPONGE_ROUTER_HH 3 | 4 | #include "network_interface.hh" 5 | 6 | #include 7 | #include 8 | 9 | //! \brief A wrapper for NetworkInterface that makes the host-side 10 | //! interface asynchronous: instead of returning received datagrams 11 | //! immediately (from the `recv_frame` method), it stores them for 12 | //! later retrieval. Otherwise, behaves identically to the underlying 13 | //! implementation of NetworkInterface. 14 | class AsyncNetworkInterface : public NetworkInterface { 15 | std::queue _datagrams_out{}; 16 | 17 | public: 18 | using NetworkInterface::NetworkInterface; 19 | 20 | //! Construct from a NetworkInterface 21 | AsyncNetworkInterface(NetworkInterface &&interface) : NetworkInterface(interface) {} 22 | 23 | //! \brief Receives and Ethernet frame and responds appropriately. 24 | 25 | //! - If type is IPv4, pushes to the `datagrams_out` queue for later retrieval by the owner. 26 | //! - If type is ARP request, learn a mapping from the "sender" fields, and send an ARP reply. 27 | //! - If type is ARP reply, learn a mapping from the "target" fields. 28 | //! 29 | //! \param[in] frame the incoming Ethernet frame 30 | void recv_frame(const EthernetFrame &frame) { 31 | auto optional_dgram = NetworkInterface::recv_frame(frame); 32 | if (optional_dgram.has_value()) { 33 | _datagrams_out.push(std::move(optional_dgram.value())); 34 | } 35 | }; 36 | 37 | //! Access queue of Internet datagrams that have been received 38 | std::queue &datagrams_out() { return _datagrams_out; } 39 | }; 40 | 41 | //! \brief A router that has multiple network interfaces and 42 | //! performs longest-prefix-match routing between them. 43 | class Router { 44 | //! The router's collection of network interfaces 45 | std::vector _interfaces{}; 46 | 47 | //! Send a single datagram from the appropriate outbound interface to the next hop, 48 | //! as specified by the route with the longest prefix_length that matches the 49 | //! datagram's destination address. 50 | void route_one_datagram(InternetDatagram &dgram); 51 | 52 | struct RouterTableEntry { 53 | const uint32_t route_prefix; 54 | const uint8_t prefix_length; 55 | const std::optional
next_hop; 56 | const size_t interface_idx; 57 | }; 58 | std::vector _router_table{}; 59 | 60 | public: 61 | //! Add an interface to the router 62 | //! \param[in] interface an already-constructed network interface 63 | //! \returns The index of the interface after it has been added to the router 64 | size_t add_interface(AsyncNetworkInterface &&interface) { 65 | _interfaces.push_back(std::move(interface)); 66 | return _interfaces.size() - 1; 67 | } 68 | 69 | //! Access an interface by index 70 | AsyncNetworkInterface &interface(const size_t N) { return _interfaces.at(N); } 71 | 72 | //! Add a route (a forwarding rule) 73 | void add_route(const uint32_t route_prefix, 74 | const uint8_t prefix_length, 75 | const std::optional
next_hop, 76 | const size_t interface_num); 77 | 78 | //! Route packets between the interfaces 79 | void route(); 80 | }; 81 | 82 | #endif // SPONGE_LIBSPONGE_ROUTER_HH 83 | -------------------------------------------------------------------------------- /libsponge/stream_reassembler.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH 2 | #define SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH 3 | 4 | #include "byte_stream.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | //! \brief A class that assembles a series of excerpts from a byte stream (possibly out of order, 11 | //! possibly overlapping) into an in-order byte stream. 12 | class StreamReassembler { 13 | private: 14 | // Your code here -- add private members as necessary. 15 | std::map _unassemble_strs; 16 | size_t _next_assembled_idx; 17 | size_t _unassembled_bytes_num; 18 | size_t _eof_idx; 19 | 20 | ByteStream _output; //!< The reassembled in-order byte stream 21 | size_t _capacity; //!< The maximum number of bytes 22 | 23 | public: 24 | //! \brief Construct a `StreamReassembler` that will store up to `capacity` bytes. 25 | //! \note This capacity limits both the bytes that have been reassembled, 26 | //! and those that have not yet been reassembled. 27 | StreamReassembler(const size_t capacity); 28 | 29 | //! \brief Receive a substring and write any newly contiguous bytes into the stream. 30 | //! 31 | //! The StreamReassembler will stay within the memory limits of the `capacity`. 32 | //! Bytes that would exceed the capacity are silently discarded. 33 | //! 34 | //! \param data the substring 35 | //! \param index indicates the index (place in sequence) of the first byte in `data` 36 | //! \param eof the last byte of `data` will be the last byte in the entire stream 37 | void push_substring(const std::string &data, const uint64_t index, const bool eof); 38 | 39 | //! \name Access the reassembled byte stream 40 | //!@{ 41 | const ByteStream &stream_out() const { return _output; } 42 | ByteStream &stream_out() { return _output; } 43 | //!@} 44 | 45 | //! The number of bytes in the substrings stored but not yet reassembled 46 | //! 47 | //! \note If the byte at a particular index has been pushed more than once, it 48 | //! should only be counted once for the purpose of this function. 49 | size_t unassembled_bytes() const; 50 | 51 | //! \brief Is the internal state empty (other than the output stream)? 52 | //! \returns `true` if no substrings are waiting to be assembled 53 | bool empty() const; 54 | }; 55 | 56 | #endif // SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH 57 | -------------------------------------------------------------------------------- /libsponge/tap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | show_usage () { 4 | echo "Usage: $0 [tapnum ...]" 5 | exit 1 6 | } 7 | 8 | start_tap () { 9 | local TAPNUM="$1" TAPDEV="tap$1" LLADDR="02:B0:1D:FA:CE:"`printf "%02x" $1` 10 | ip tuntap add mode tap user "${SUDO_USER}" name "${TAPDEV}" 11 | ip link set "${TAPDEV}" address "${LLADDR}" 12 | 13 | ip addr add "${TUN_IP_PREFIX}.${TAPNUM}.1/24" dev "${TAPDEV}" 14 | ip link set dev "${TAPDEV}" up 15 | ip route change "${TUN_IP_PREFIX}.${TAPNUM}.0/24" dev "${TAPDEV}" rto_min 10ms 16 | 17 | # Apply NAT (masquerading) only to traffic from CS144's network devices 18 | iptables -t nat -A PREROUTING -s ${TUN_IP_PREFIX}.${TAPNUM}.0/24 -j CONNMARK --set-mark ${TAPNUM} 19 | iptables -t nat -A POSTROUTING -j MASQUERADE -m connmark --mark ${TAPNUM} 20 | echo 1 > /proc/sys/net/ipv4/ip_forward 21 | } 22 | 23 | stop_tap () { 24 | local TAPDEV="tap$1" 25 | iptables -t nat -D PREROUTING -s ${TUN_IP_PREFIX}.${1}.0/24 -j CONNMARK --set-mark ${1} 26 | iptables -t nat -D POSTROUTING -j MASQUERADE -m connmark --mark ${1} 27 | ip tuntap del mode tap name "$TAPDEV" 28 | } 29 | 30 | start_all () { 31 | while [ ! -z "$1" ]; do 32 | local INTF="$1"; shift 33 | start_tap "$INTF" 34 | done 35 | } 36 | 37 | stop_all () { 38 | while [ ! -z "$1" ]; do 39 | local INTF="$1"; shift 40 | stop_tap "$INTF" 41 | done 42 | } 43 | 44 | restart_all() { 45 | stop_all "$@" 46 | start_all "$@" 47 | } 48 | 49 | check_tap () { 50 | [ "$#" != 1 ] && { echo "bad params in check_tap"; exit 1; } 51 | local TAPDEV="tap${1}" 52 | # make sure tap is healthy: device is up, ip_forward is set, and iptables is configured 53 | ip link show ${TAPDEV} &>/dev/null || return 1 54 | [ "$(cat /proc/sys/net/ipv4/ip_forward)" = "1" ] || return 2 55 | } 56 | 57 | check_sudo () { 58 | if [ "$SUDO_USER" = "root" ]; then 59 | echo "please execute this script as a regular user, not as root" 60 | exit 1 61 | fi 62 | if [ -z "$SUDO_USER" ]; then 63 | # if the user didn't call us with sudo, re-execute 64 | exec sudo $0 "$MODE" "$@" 65 | fi 66 | } 67 | 68 | # check arguments 69 | if [ -z "$1" ] || ([ "$1" != "start" ] && [ "$1" != "stop" ] && [ "$1" != "restart" ] && [ "$1" != "check" ]); then 70 | show_usage 71 | fi 72 | MODE=$1; shift 73 | 74 | # set default argument 75 | if [ "$#" = "0" ]; then 76 | set -- 10 77 | fi 78 | 79 | # execute 'check' before trying to sudo 80 | # - like start, but exit successfully if everything is OK 81 | if [ "$MODE" = "check" ]; then 82 | declare -a INTFS 83 | MODE="start" 84 | while [ ! -z "$1" ]; do 85 | INTF="$1"; shift 86 | check_tap ${INTF} 87 | RET=$? 88 | if [ "$RET" = "0" ]; then 89 | continue 90 | fi 91 | 92 | if [ "$((RET > 1))" = "1" ]; then 93 | MODE="restart" 94 | fi 95 | INTFS+=($INTF) 96 | done 97 | 98 | # address only the interfaces that need it 99 | set -- "${INTFS[@]}" 100 | if [ "$#" = "0" ]; then 101 | exit 0 102 | fi 103 | echo -e "[$0] Bringing up tunnels ${INTFS[@]}:" 104 | fi 105 | 106 | # sudo if necessary 107 | check_sudo "$@" 108 | 109 | # get configuration 110 | . "$(dirname "$0")"/etc/tunconfig 111 | 112 | # start, stop, or restart all intfs 113 | eval "${MODE}_all" "$@" 114 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/arp_message.cc: -------------------------------------------------------------------------------- 1 | #include "arp_message.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | ParseResult ARPMessage::parse(const Buffer buffer) { 10 | NetParser p{buffer}; 11 | 12 | if (p.buffer().size() < ARPMessage::LENGTH) { 13 | return ParseResult::PacketTooShort; 14 | } 15 | 16 | hardware_type = p.u16(); 17 | protocol_type = p.u16(); 18 | hardware_address_size = p.u8(); 19 | protocol_address_size = p.u8(); 20 | opcode = p.u16(); 21 | 22 | if (not supported()) { 23 | return ParseResult::Unsupported; 24 | } 25 | 26 | // read sender addresses (Ethernet and IP) 27 | for (auto &byte : sender_ethernet_address) { 28 | byte = p.u8(); 29 | } 30 | sender_ip_address = p.u32(); 31 | 32 | // read target addresses (Ethernet and IP) 33 | for (auto &byte : target_ethernet_address) { 34 | byte = p.u8(); 35 | } 36 | target_ip_address = p.u32(); 37 | 38 | return p.get_error(); 39 | } 40 | 41 | bool ARPMessage::supported() const { 42 | return hardware_type == TYPE_ETHERNET and protocol_type == EthernetHeader::TYPE_IPv4 and 43 | hardware_address_size == sizeof(EthernetHeader::src) and protocol_address_size == sizeof(IPv4Header::src) and 44 | ((opcode == OPCODE_REQUEST) or (opcode == OPCODE_REPLY)); 45 | } 46 | 47 | string ARPMessage::serialize() const { 48 | if (not supported()) { 49 | throw runtime_error( 50 | "ARPMessage::serialize(): unsupported field combination (must be Ethernet/IP, and request or reply)"); 51 | } 52 | 53 | string ret; 54 | NetUnparser::u16(ret, hardware_type); 55 | NetUnparser::u16(ret, protocol_type); 56 | NetUnparser::u8(ret, hardware_address_size); 57 | NetUnparser::u8(ret, protocol_address_size); 58 | NetUnparser::u16(ret, opcode); 59 | 60 | /* write sender addresses */ 61 | for (auto &byte : sender_ethernet_address) { 62 | NetUnparser::u8(ret, byte); 63 | } 64 | NetUnparser::u32(ret, sender_ip_address); 65 | 66 | /* write target addresses */ 67 | for (auto &byte : target_ethernet_address) { 68 | NetUnparser::u8(ret, byte); 69 | } 70 | NetUnparser::u32(ret, target_ip_address); 71 | 72 | return ret; 73 | } 74 | 75 | string ARPMessage::to_string() const { 76 | stringstream ss{}; 77 | string opcode_str = "(unknown type)"; 78 | if (opcode == OPCODE_REQUEST) { 79 | opcode_str = "REQUEST"; 80 | } 81 | if (opcode == OPCODE_REPLY) { 82 | opcode_str = "REPLY"; 83 | } 84 | ss << "opcode=" << opcode_str << ", sender=" << ::to_string(sender_ethernet_address) << "/" 85 | << inet_ntoa({htobe32(sender_ip_address)}) << ", target=" << ::to_string(target_ethernet_address) << "/" 86 | << inet_ntoa({htobe32(target_ip_address)}); 87 | return ss.str(); 88 | } 89 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/arp_message.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_ARP_MESSAGE_HH 2 | #define SPONGE_LIBSPONGE_ARP_MESSAGE_HH 3 | 4 | #include "ethernet_header.hh" 5 | #include "ipv4_header.hh" 6 | 7 | using EthernetAddress = std::array; 8 | 9 | //! \brief [ARP](\ref rfc::rfc826) message 10 | struct ARPMessage { 11 | static constexpr size_t LENGTH = 28; //!< ARP message length in bytes 12 | static constexpr uint16_t TYPE_ETHERNET = 1; //!< ARP type for Ethernet/Wi-Fi as link-layer protocol 13 | static constexpr uint16_t OPCODE_REQUEST = 1; 14 | static constexpr uint16_t OPCODE_REPLY = 2; 15 | 16 | //! \name ARPheader fields 17 | //!@{ 18 | uint16_t hardware_type = TYPE_ETHERNET; //!< Type of the link-layer protocol (generally Ethernet/Wi-Fi) 19 | uint16_t protocol_type = EthernetHeader::TYPE_IPv4; //!< Type of the Internet-layer protocol (generally IPv4) 20 | uint8_t hardware_address_size = sizeof(EthernetHeader::src); 21 | uint8_t protocol_address_size = sizeof(IPv4Header::src); 22 | uint16_t opcode{}; //!< Request or reply 23 | 24 | EthernetAddress sender_ethernet_address{}; 25 | uint32_t sender_ip_address{}; 26 | 27 | EthernetAddress target_ethernet_address{}; 28 | uint32_t target_ip_address{}; 29 | //!@} 30 | 31 | //! Parse the ARP message from a string 32 | ParseResult parse(const Buffer buffer); 33 | 34 | //! Serialize the ARP message to a string 35 | std::string serialize() const; 36 | 37 | //! Return a string containing the ARP message in human-readable format 38 | std::string to_string() const; 39 | 40 | //! Is this type of ARP message supported by the parser? 41 | bool supported() const; 42 | }; 43 | 44 | //! \struct ARPMessage 45 | //! This struct can be used to parse an existing ARP message or to create a new one. 46 | 47 | #endif // SPONGE_LIBSPONGE_ETHERNET_HEADER_HH 48 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/ethernet_frame.cc: -------------------------------------------------------------------------------- 1 | #include "ethernet_frame.hh" 2 | 3 | #include "parser.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | ParseResult EthernetFrame::parse(const Buffer buffer) { 12 | NetParser p{buffer}; 13 | _header.parse(p); 14 | _payload = p.buffer(); 15 | 16 | return p.get_error(); 17 | } 18 | 19 | BufferList EthernetFrame::serialize() const { 20 | BufferList ret; 21 | ret.append(_header.serialize()); 22 | ret.append(_payload); 23 | return ret; 24 | } 25 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/ethernet_frame.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_ETHERNET_FRAME_HH 2 | #define SPONGE_LIBSPONGE_ETHERNET_FRAME_HH 3 | 4 | #include "buffer.hh" 5 | #include "ethernet_header.hh" 6 | 7 | //! \brief Ethernet frame 8 | class EthernetFrame { 9 | private: 10 | EthernetHeader _header{}; 11 | BufferList _payload{}; 12 | 13 | public: 14 | //! \brief Parse the frame from a string 15 | ParseResult parse(const Buffer buffer); 16 | 17 | //! \brief Serialize the frame to a string 18 | BufferList serialize() const; 19 | 20 | //! \name Accessors 21 | //!@{ 22 | const EthernetHeader &header() const { return _header; } 23 | EthernetHeader &header() { return _header; } 24 | 25 | const BufferList &payload() const { return _payload; } 26 | BufferList &payload() { return _payload; } 27 | //!@} 28 | }; 29 | 30 | #endif // SPONGE_LIBSPONGE_ETHERNET_FRAME_HH 31 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/ethernet_header.cc: -------------------------------------------------------------------------------- 1 | #include "ethernet_header.hh" 2 | 3 | #include "util.hh" 4 | 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | ParseResult EthernetHeader::parse(NetParser &p) { 11 | if (p.buffer().size() < EthernetHeader::LENGTH) { 12 | return ParseResult::PacketTooShort; 13 | } 14 | 15 | /* read destination address */ 16 | for (auto &byte : dst) { 17 | byte = p.u8(); 18 | } 19 | 20 | /* read source address */ 21 | for (auto &byte : src) { 22 | byte = p.u8(); 23 | } 24 | 25 | /* read the frame's type (e.g. IPv4, ARP, or something else) */ 26 | type = p.u16(); 27 | 28 | return p.get_error(); 29 | } 30 | 31 | string EthernetHeader::serialize() const { 32 | string ret; 33 | ret.reserve(LENGTH); 34 | 35 | /* write destination address */ 36 | for (auto &byte : dst) { 37 | NetUnparser::u8(ret, byte); 38 | } 39 | 40 | /* write source address */ 41 | for (auto &byte : src) { 42 | NetUnparser::u8(ret, byte); 43 | } 44 | 45 | /* write the frame's type (e.g. IPv4, ARP or something else) */ 46 | NetUnparser::u16(ret, type); 47 | 48 | return ret; 49 | } 50 | 51 | //! \returns A string with a textual representation of an Ethernet address 52 | string to_string(const EthernetAddress address) { 53 | stringstream ss{}; 54 | for (auto it = address.begin(); it != address.end(); it++) { 55 | ss.width(2); 56 | ss << setfill('0') << hex << int(*it); 57 | if (it != address.end() - 1) { 58 | ss << ":"; 59 | } 60 | } 61 | return ss.str(); 62 | } 63 | 64 | //! \returns A string with the header's contents 65 | string EthernetHeader::to_string() const { 66 | stringstream ss{}; 67 | ss << "dst=" << ::to_string(dst); 68 | ss << ", src=" << ::to_string(src); 69 | ss << ", type="; 70 | switch (type) { 71 | case TYPE_IPv4: 72 | ss << "IPv4"; 73 | break; 74 | case TYPE_ARP: 75 | ss << "ARP"; 76 | break; 77 | default: 78 | ss << "[unknown type " << hex << type << "!]"; 79 | break; 80 | } 81 | 82 | return ss.str(); 83 | } 84 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/ethernet_header.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_ETHERNET_HEADER_HH 2 | #define SPONGE_LIBSPONGE_ETHERNET_HEADER_HH 3 | 4 | #include "parser.hh" 5 | 6 | #include 7 | 8 | //! Helper type for an Ethernet address (an array of six bytes) 9 | using EthernetAddress = std::array; 10 | 11 | //! Ethernet broadcast address (ff:ff:ff:ff:ff:ff) 12 | constexpr EthernetAddress ETHERNET_BROADCAST = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; 13 | 14 | //! Printable representation of an EthernetAddress 15 | std::string to_string(const EthernetAddress address); 16 | 17 | //! \brief Ethernet frame header 18 | struct EthernetHeader { 19 | static constexpr size_t LENGTH = 14; //!< Ethernet header length in bytes 20 | static constexpr uint16_t TYPE_IPv4 = 0x800; //!< Type number for [IPv4](\ref rfc::rfc791) 21 | static constexpr uint16_t TYPE_ARP = 0x806; //!< Type number for [ARP](\ref rfc::rfc826) 22 | 23 | //! \name Ethernet header fields 24 | //!@{ 25 | EthernetAddress dst; 26 | EthernetAddress src; 27 | uint16_t type; 28 | //!@} 29 | 30 | //! Parse the Ethernet fields from the provided NetParser 31 | ParseResult parse(NetParser &p); 32 | 33 | //! Serialize the Ethernet fields to a string 34 | std::string serialize() const; 35 | 36 | //! Return a string containing a header in human-readable format 37 | std::string to_string() const; 38 | }; 39 | 40 | //! \struct EthernetHeader 41 | //! This struct can be used to parse an existing Ethernet header or to create a new one. 42 | 43 | #endif // SPONGE_LIBSPONGE_ETHERNET_HEADER_HH 44 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/fd_adapter.cc: -------------------------------------------------------------------------------- 1 | #include "fd_adapter.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | //! \details This function first attempts to parse a TCP segment from the next UDP 10 | //! payload recv()d from the socket. 11 | //! 12 | //! If this succeeds, it then checks that the received segment is related to the 13 | //! current connection. When a TCP connection has been established, this means 14 | //! checking that the source and destination ports in the TCP header are correct. 15 | //! 16 | //! If the TCP FSM is listening (i.e., TCPOverUDPSocketAdapter::_listen is `true`) 17 | //! and the TCP segment read from the wire includes a SYN, this function clears the 18 | //! `_listen` flag and calls calls connect() on the underlying UDP socket, with 19 | //! the result that future outgoing segments go to the sender of the SYN segment. 20 | //! \returns a std::optional that is empty if the segment was invalid or unrelated 21 | optional TCPOverUDPSocketAdapter::read() { 22 | auto datagram = _sock.recv(); 23 | 24 | // is it for us? 25 | if (not listening() and (datagram.source_address != config().destination)) { 26 | return {}; 27 | } 28 | 29 | // is the payload a valid TCP segment? 30 | TCPSegment seg; 31 | if (ParseResult::NoError != seg.parse(move(datagram.payload), 0)) { 32 | return {}; 33 | } 34 | 35 | // should we target this source in all future replies? 36 | if (listening()) { 37 | if (seg.header().syn and not seg.header().rst) { 38 | config_mutable().destination = datagram.source_address; 39 | set_listening(false); 40 | } else { 41 | return {}; 42 | } 43 | } 44 | 45 | return seg; 46 | } 47 | 48 | //! Serialize a TCP segment and send it as the payload of a UDP datagram. 49 | //! \param[in] seg is the TCP segment to write 50 | void TCPOverUDPSocketAdapter::write(TCPSegment &seg) { 51 | seg.header().sport = config().source.port(); 52 | seg.header().dport = config().destination.port(); 53 | _sock.sendto(config().destination, seg.serialize(0)); 54 | } 55 | 56 | //! Specialize LossyFdAdapter to TCPOverUDPSocketAdapter 57 | template class LossyFdAdapter; 58 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/fd_adapter.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_FD_ADAPTER_HH 2 | #define SPONGE_LIBSPONGE_FD_ADAPTER_HH 3 | 4 | #include "file_descriptor.hh" 5 | #include "lossy_fd_adapter.hh" 6 | #include "socket.hh" 7 | #include "tcp_config.hh" 8 | #include "tcp_header.hh" 9 | #include "tcp_segment.hh" 10 | 11 | #include 12 | #include 13 | 14 | //! \brief Basic functionality for file descriptor adaptors 15 | //! \details See TCPOverUDPSocketAdapter and TCPOverIPv4OverTunFdAdapter for more information. 16 | class FdAdapterBase { 17 | private: 18 | FdAdapterConfig _cfg{}; //!< Configuration values 19 | bool _listen = false; //!< Is the connected TCP FSM in listen state? 20 | 21 | protected: 22 | FdAdapterConfig &config_mutable() { return _cfg; } 23 | 24 | public: 25 | //! \brief Set the listening flag 26 | //! \param[in] l is the new value for the flag 27 | void set_listening(const bool l) { _listen = l; } 28 | 29 | //! \brief Get the listening flag 30 | //! \returns whether the FdAdapter is listening for a new connection 31 | bool listening() const { return _listen; } 32 | 33 | //! \brief Get the current configuration 34 | //! \returns a const reference 35 | const FdAdapterConfig &config() const { return _cfg; } 36 | 37 | //! \brief Get the current configuration (mutable) 38 | //! \returns a mutable reference 39 | FdAdapterConfig &config_mut() { return _cfg; } 40 | 41 | //! Called periodically when time elapses 42 | void tick(const size_t) {} 43 | }; 44 | 45 | //! \brief A FD adaptor that reads and writes TCP segments in UDP payloads 46 | class TCPOverUDPSocketAdapter : public FdAdapterBase { 47 | private: 48 | UDPSocket _sock; 49 | 50 | public: 51 | //! Construct from a UDPSocket sliced into a FileDescriptor 52 | explicit TCPOverUDPSocketAdapter(UDPSocket &&sock) : _sock(std::move(sock)) {} 53 | 54 | //! Attempts to read and return a TCP segment related to the current connection from a UDP payload 55 | std::optional read(); 56 | 57 | //! Writes a TCP segment into a UDP payload 58 | void write(TCPSegment &seg); 59 | 60 | //! Access the underlying UDP socket 61 | operator UDPSocket &() { return _sock; } 62 | 63 | //! Access the underlying UDP socket 64 | operator const UDPSocket &() const { return _sock; } 65 | }; 66 | 67 | //! Typedef for TCPOverUDPSocketAdapter 68 | using LossyTCPOverUDPSocketAdapter = LossyFdAdapter; 69 | 70 | #endif // SPONGE_LIBSPONGE_FD_ADAPTER_HH 71 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/ipv4_datagram.cc: -------------------------------------------------------------------------------- 1 | #include "ipv4_datagram.hh" 2 | 3 | #include "parser.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | ParseResult IPv4Datagram::parse(const Buffer buffer) { 12 | NetParser p{buffer}; 13 | _header.parse(p); 14 | _payload = p.buffer(); 15 | 16 | if (_payload.size() != _header.payload_length()) { 17 | return ParseResult::PacketTooShort; 18 | } 19 | 20 | return p.get_error(); 21 | } 22 | 23 | BufferList IPv4Datagram::serialize() const { 24 | if (_payload.size() != _header.payload_length()) { 25 | throw runtime_error("IPv4Datagram::serialize: payload is wrong size"); 26 | } 27 | 28 | IPv4Header header_out = _header; 29 | header_out.cksum = 0; 30 | const string header_zero_checksum = header_out.serialize(); 31 | 32 | // calculate checksum -- taken over header only 33 | InternetChecksum check; 34 | check.add(header_zero_checksum); 35 | header_out.cksum = check.value(); 36 | 37 | BufferList ret; 38 | ret.append(header_out.serialize()); 39 | ret.append(_payload); 40 | return ret; 41 | } 42 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/ipv4_datagram.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_IPV4_DATAGRAM_HH 2 | #define SPONGE_LIBSPONGE_IPV4_DATAGRAM_HH 3 | 4 | #include "buffer.hh" 5 | #include "ipv4_header.hh" 6 | 7 | //! \brief [IPv4](\ref rfc::rfc791) Internet datagram 8 | class IPv4Datagram { 9 | private: 10 | IPv4Header _header{}; 11 | BufferList _payload{}; 12 | 13 | public: 14 | //! \brief Parse the segment from a string 15 | ParseResult parse(const Buffer buffer); 16 | 17 | //! \brief Serialize the segment to a string 18 | BufferList serialize() const; 19 | 20 | //! \name Accessors 21 | //!@{ 22 | const IPv4Header &header() const { return _header; } 23 | IPv4Header &header() { return _header; } 24 | 25 | const BufferList &payload() const { return _payload; } 26 | BufferList &payload() { return _payload; } 27 | //!@} 28 | }; 29 | 30 | using InternetDatagram = IPv4Datagram; 31 | 32 | #endif // SPONGE_LIBSPONGE_IPV4_DATAGRAM_HH 33 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/ipv4_header.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_IPV4_HEADER_HH 2 | #define SPONGE_LIBSPONGE_IPV4_HEADER_HH 3 | 4 | #include "parser.hh" 5 | 6 | //! \brief [IPv4](\ref rfc::rfc791) Internet datagram header 7 | //! \note IP options are not supported 8 | struct IPv4Header { 9 | static constexpr size_t LENGTH = 20; //!< [IPv4](\ref rfc::rfc791) header length, not including options 10 | static constexpr uint8_t DEFAULT_TTL = 128; //!< A reasonable default TTL value 11 | static constexpr uint8_t PROTO_TCP = 6; //!< Protocol number for [tcp](\ref rfc::rfc793) 12 | 13 | //! \struct IPv4Header 14 | //! ~~~{.txt} 15 | //! 0 1 2 3 16 | //! 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 17 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 18 | //! |Version| IHL |Type of Service| Total Length | 19 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | //! | Identification |Flags| Fragment Offset | 21 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | //! | Time to Live | Protocol | Header Checksum | 23 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 24 | //! | Source Address | 25 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 26 | //! | Destination Address | 27 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 28 | //! | Options | Padding | 29 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 30 | //! ~~~ 31 | 32 | //! \name IPv4 Header fields 33 | //!@{ 34 | uint8_t ver = 4; //!< IP version 35 | uint8_t hlen = LENGTH / 4; //!< header length (multiples of 32 bits) 36 | uint8_t tos = 0; //!< type of service 37 | uint16_t len = 0; //!< total length of packet 38 | uint16_t id = 0; //!< identification number 39 | bool df = true; //!< don't fragment flag 40 | bool mf = false; //!< more fragments flag 41 | uint16_t offset = 0; //!< fragment offset field 42 | uint8_t ttl = DEFAULT_TTL; //!< time to live field 43 | uint8_t proto = PROTO_TCP; //!< protocol field 44 | uint16_t cksum = 0; //!< checksum field 45 | uint32_t src = 0; //!< src address 46 | uint32_t dst = 0; //!< dst address 47 | //!@} 48 | 49 | //! Parse the IP fields from the provided NetParser 50 | ParseResult parse(NetParser &p); 51 | 52 | //! Serialize the IP fields 53 | std::string serialize() const; 54 | 55 | //! Length of the payload 56 | uint16_t payload_length() const; 57 | 58 | //! [pseudo-header's](\ref rfc::rfc793) contribution to the TCP checksum 59 | uint32_t pseudo_cksum() const; 60 | 61 | //! Return a string containing a header in human-readable format 62 | std::string to_string() const; 63 | 64 | //! Return a string containing a human-readable summary of the header 65 | std::string summary() const; 66 | }; 67 | 68 | //! \struct IPv4Header 69 | //! This struct can be used to parse an existing IP header or to create a new one. 70 | 71 | #endif // SPONGE_LIBSPONGE_IPV4_HEADER_HH 72 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/lossy_fd_adapter.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_LOSSY_FD_ADAPTER_HH 2 | #define SPONGE_LIBSPONGE_LOSSY_FD_ADAPTER_HH 3 | 4 | #include "file_descriptor.hh" 5 | #include "tcp_config.hh" 6 | #include "tcp_segment.hh" 7 | #include "util.hh" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | //! An adapter class that adds random dropping behavior to an FD adapter 14 | template 15 | class LossyFdAdapter { 16 | private: 17 | //! Fast RNG used by _should_drop() 18 | std::mt19937 _rand{get_random_generator()}; 19 | 20 | //! The underlying FD adapter 21 | AdapterT _adapter; 22 | 23 | //! \brief Determine whether or not to drop a given read or write 24 | //! \param[in] uplink is `true` to use the uplink loss probability, else use the downlink loss probability 25 | //! \returns `true` if the segment should be dropped 26 | bool _should_drop(bool uplink) { 27 | const auto &cfg = _adapter.config(); 28 | const uint16_t loss = uplink ? cfg.loss_rate_up : cfg.loss_rate_dn; 29 | return loss != 0 && uint16_t(_rand()) < loss; 30 | } 31 | 32 | public: 33 | //! Conversion to a FileDescriptor by returning the underlying AdapterT 34 | operator const FileDescriptor &() const { return _adapter; } 35 | 36 | //! Construct from a FileDescriptor appropriate to the AdapterT constructor 37 | explicit LossyFdAdapter(AdapterT &&adapter) : _adapter(std::move(adapter)) {} 38 | 39 | //! \brief Read from the underlying AdapterT instance, potentially dropping the read datagram 40 | //! \returns std::optional that is empty if the segment was dropped or if 41 | //! the underlying AdapterT returned an empty value 42 | std::optional read() { 43 | auto ret = _adapter.read(); 44 | if (_should_drop(false)) { 45 | return {}; 46 | } 47 | return ret; 48 | } 49 | 50 | //! \brief Write to the underlying AdapterT instance, potentially dropping the datagram to be written 51 | //! \param[in] seg is the packet to either write or drop 52 | void write(TCPSegment &seg) { 53 | if (_should_drop(true)) { 54 | return; 55 | } 56 | return _adapter.write(seg); 57 | } 58 | 59 | //! \name 60 | //! Passthrough functions to the underlying AdapterT instance 61 | 62 | //!@{ 63 | void set_listening(const bool l) { _adapter.set_listening(l); } //!< FdAdapterBase::set_listening passthrough 64 | const FdAdapterConfig &config() const { return _adapter.config(); } //!< FdAdapterBase::config passthrough 65 | FdAdapterConfig &config_mut() { return _adapter.config_mut(); } //!< FdAdapterBase::config_mut passthrough 66 | void tick(const size_t ms_since_last_tick) { 67 | _adapter.tick(ms_since_last_tick); 68 | } //!< FdAdapterBase::tick passthrough 69 | //!@} 70 | }; 71 | 72 | #endif // SPONGE_LIBSPONGE_LOSSY_FD_ADAPTER_HH 73 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tcp_config.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TCP_CONFIG_HH 2 | #define SPONGE_LIBSPONGE_TCP_CONFIG_HH 3 | 4 | #include "address.hh" 5 | #include "wrapping_integers.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | //! Config for TCP sender and receiver 12 | class TCPConfig { 13 | public: 14 | static constexpr size_t DEFAULT_CAPACITY = 64000; //!< Default capacity 15 | static constexpr size_t MAX_PAYLOAD_SIZE = 1000; //!< Conservative max payload size for real Internet 16 | static constexpr uint16_t TIMEOUT_DFLT = 1000; //!< Default re-transmit timeout is 1 second 17 | static constexpr unsigned MAX_RETX_ATTEMPTS = 8; //!< Maximum re-transmit attempts before giving up 18 | 19 | uint16_t rt_timeout = TIMEOUT_DFLT; //!< Initial value of the retransmission timeout, in milliseconds 20 | size_t recv_capacity = DEFAULT_CAPACITY; //!< Receive capacity, in bytes 21 | size_t send_capacity = DEFAULT_CAPACITY; //!< Sender capacity, in bytes 22 | std::optional fixed_isn{}; 23 | }; 24 | 25 | //! Config for classes derived from FdAdapter 26 | class FdAdapterConfig { 27 | public: 28 | Address source{"0", 0}; //!< Source address and port 29 | Address destination{"0", 0}; //!< Destination address and port 30 | 31 | uint16_t loss_rate_dn = 0; //!< Downlink loss rate (for LossyFdAdapter) 32 | uint16_t loss_rate_up = 0; //!< Uplink loss rate (for LossyFdAdapter) 33 | }; 34 | 35 | #endif // SPONGE_LIBSPONGE_TCP_CONFIG_HH 36 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tcp_header.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TCP_HEADER_HH 2 | #define SPONGE_LIBSPONGE_TCP_HEADER_HH 3 | 4 | #include "parser.hh" 5 | #include "wrapping_integers.hh" 6 | 7 | //! \brief [TCP](\ref rfc::rfc793) segment header 8 | //! \note TCP options are not supported 9 | struct TCPHeader { 10 | static constexpr size_t LENGTH = 20; //!< [TCP](\ref rfc::rfc793) header length, not including options 11 | 12 | //! \struct TCPHeader 13 | //! ~~~{.txt} 14 | //! 0 1 2 3 15 | //! 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 16 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | //! | Source Port | Destination Port | 18 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | //! | Sequence Number | 20 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | //! | Acknowledgment Number | 22 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 23 | //! | Data | |U|A|P|R|S|F| | 24 | //! | Offset| Reserved |R|C|S|S|Y|I| Window | 25 | //! | | |G|K|H|T|N|N| | 26 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 27 | //! | Checksum | Urgent Pointer | 28 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 29 | //! | Options | Padding | 30 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 31 | //! | data | 32 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 33 | //! ~~~ 34 | 35 | //! \name TCP Header fields 36 | //!@{ 37 | uint16_t sport = 0; //!< source port 38 | uint16_t dport = 0; //!< destination port 39 | WrappingInt32 seqno{0}; //!< sequence number 40 | WrappingInt32 ackno{0}; //!< ack number 41 | uint8_t doff = LENGTH / 4; //!< data offset 42 | bool urg = false; //!< urgent flag 43 | bool ack = false; //!< ack flag 44 | bool psh = false; //!< push flag 45 | bool rst = false; //!< rst flag 46 | bool syn = false; //!< syn flag 47 | bool fin = false; //!< fin flag 48 | uint16_t win = 0; //!< window size 49 | uint16_t cksum = 0; //!< checksum 50 | uint16_t uptr = 0; //!< urgent pointer 51 | //!@} 52 | 53 | //! Parse the TCP fields from the provided NetParser 54 | ParseResult parse(NetParser &p); 55 | 56 | //! Serialize the TCP fields 57 | std::string serialize() const; 58 | 59 | //! Return a string containing a header in human-readable format 60 | std::string to_string() const; 61 | 62 | //! Return a string containing a human-readable summary of the header 63 | std::string summary() const; 64 | 65 | bool operator==(const TCPHeader &other) const; 66 | }; 67 | 68 | #endif // SPONGE_LIBSPONGE_TCP_HEADER_HH 69 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tcp_over_ip.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_over_ip.hh" 2 | 3 | #include "ipv4_datagram.hh" 4 | #include "ipv4_header.hh" 5 | #include "parser.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | //! \details This function attempts to parse a TCP segment from 15 | //! the IP datagram's payload. 16 | //! 17 | //! If this succeeds, it then checks that the received segment is related to the 18 | //! current connection. When a TCP connection has been established, this means 19 | //! checking that the source and destination ports in the TCP header are correct. 20 | //! 21 | //! If the TCP connection is listening (i.e., TCPOverIPv4OverTunFdAdapter::_listen is `true`) 22 | //! and the TCP segment read from the wire includes a SYN, this function clears the 23 | //! `_listen` flag and records the source and destination addresses and port numbers 24 | //! from the TCP header; it uses this information to filter future reads. 25 | //! \returns a std::optional that is empty if the segment was invalid or unrelated 26 | optional TCPOverIPv4Adapter::unwrap_tcp_in_ip(const InternetDatagram &ip_dgram) { 27 | // is the IPv4 datagram for us? 28 | // Note: it's valid to bind to address "0" (INADDR_ANY) and reply from actual address contacted 29 | if (not listening() and (ip_dgram.header().dst != config().source.ipv4_numeric())) { 30 | return {}; 31 | } 32 | 33 | // is the IPv4 datagram from our peer? 34 | if (not listening() and (ip_dgram.header().src != config().destination.ipv4_numeric())) { 35 | return {}; 36 | } 37 | 38 | // does the IPv4 datagram claim that its payload is a TCP segment? 39 | if (ip_dgram.header().proto != IPv4Header::PROTO_TCP) { 40 | return {}; 41 | } 42 | 43 | // is the payload a valid TCP segment? 44 | TCPSegment tcp_seg; 45 | if (ParseResult::NoError != tcp_seg.parse(ip_dgram.payload(), ip_dgram.header().pseudo_cksum())) { 46 | return {}; 47 | } 48 | 49 | // is the TCP segment for us? 50 | if (tcp_seg.header().dport != config().source.port()) { 51 | return {}; 52 | } 53 | 54 | // should we target this source addr/port (and use its destination addr as our source) in reply? 55 | if (listening()) { 56 | if (tcp_seg.header().syn and not tcp_seg.header().rst) { 57 | config_mutable().source = {inet_ntoa({htobe32(ip_dgram.header().dst)}), config().source.port()}; 58 | config_mutable().destination = {inet_ntoa({htobe32(ip_dgram.header().src)}), tcp_seg.header().sport}; 59 | set_listening(false); 60 | } else { 61 | return {}; 62 | } 63 | } 64 | 65 | // is the TCP segment from our peer? 66 | if (tcp_seg.header().sport != config().destination.port()) { 67 | return {}; 68 | } 69 | 70 | return tcp_seg; 71 | } 72 | 73 | //! Takes a TCP segment, sets port numbers as necessary, and wraps it in an IPv4 datagram 74 | //! \param[in] seg is the TCP segment to convert 75 | InternetDatagram TCPOverIPv4Adapter::wrap_tcp_in_ip(TCPSegment &seg) { 76 | // set the port numbers in the TCP segment 77 | seg.header().sport = config().source.port(); 78 | seg.header().dport = config().destination.port(); 79 | 80 | // create an Internet Datagram and set its addresses and length 81 | InternetDatagram ip_dgram; 82 | ip_dgram.header().src = config().source.ipv4_numeric(); 83 | ip_dgram.header().dst = config().destination.ipv4_numeric(); 84 | ip_dgram.header().len = ip_dgram.header().hlen * 4 + seg.header().doff * 4 + seg.payload().size(); 85 | 86 | // set payload, calculating TCP checksum using information from IP header 87 | ip_dgram.payload() = seg.serialize(ip_dgram.header().pseudo_cksum()); 88 | 89 | return ip_dgram; 90 | } 91 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tcp_over_ip.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TCP_OVER_IP_HH 2 | #define SPONGE_LIBSPONGE_TCP_OVER_IP_HH 3 | 4 | #include "buffer.hh" 5 | #include "fd_adapter.hh" 6 | #include "ipv4_datagram.hh" 7 | #include "tcp_segment.hh" 8 | 9 | #include 10 | 11 | //! \brief A converter from TCP segments to serialized IPv4 datagrams 12 | class TCPOverIPv4Adapter : public FdAdapterBase { 13 | public: 14 | std::optional unwrap_tcp_in_ip(const InternetDatagram &ip_dgram); 15 | 16 | InternetDatagram wrap_tcp_in_ip(TCPSegment &seg); 17 | }; 18 | 19 | #endif // SPONGE_LIBSPONGE_TCP_OVER_IP_HH 20 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tcp_segment.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_segment.hh" 2 | 3 | #include "parser.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | 8 | using namespace std; 9 | 10 | //! \param[in] buffer string/Buffer to be parsed 11 | //! \param[in] datagram_layer_checksum pseudo-checksum from the lower-layer protocol 12 | ParseResult TCPSegment::parse(const Buffer buffer, const uint32_t datagram_layer_checksum) { 13 | InternetChecksum check(datagram_layer_checksum); 14 | check.add(buffer); 15 | if (check.value()) { 16 | return ParseResult::BadChecksum; 17 | } 18 | 19 | NetParser p{buffer}; 20 | _header.parse(p); 21 | _payload = p.buffer(); 22 | return p.get_error(); 23 | } 24 | 25 | size_t TCPSegment::length_in_sequence_space() const { 26 | return payload().str().size() + (header().syn ? 1 : 0) + (header().fin ? 1 : 0); 27 | } 28 | 29 | //! \param[in] datagram_layer_checksum pseudo-checksum from the lower-layer protocol 30 | BufferList TCPSegment::serialize(const uint32_t datagram_layer_checksum) const { 31 | TCPHeader header_out = _header; 32 | header_out.cksum = 0; 33 | 34 | // calculate checksum -- taken over entire segment 35 | InternetChecksum check(datagram_layer_checksum); 36 | check.add(header_out.serialize()); 37 | check.add(_payload); 38 | header_out.cksum = check.value(); 39 | 40 | BufferList ret; 41 | ret.append(header_out.serialize()); 42 | ret.append(_payload); 43 | 44 | return ret; 45 | } 46 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tcp_segment.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TCP_SEGMENT_HH 2 | #define SPONGE_LIBSPONGE_TCP_SEGMENT_HH 3 | 4 | #include "buffer.hh" 5 | #include "tcp_header.hh" 6 | 7 | #include 8 | 9 | //! \brief [TCP](\ref rfc::rfc793) segment 10 | class TCPSegment { 11 | private: 12 | TCPHeader _header{}; 13 | Buffer _payload{}; 14 | 15 | public: 16 | //! \brief Parse the segment from a string 17 | ParseResult parse(const Buffer buffer, const uint32_t datagram_layer_checksum = 0); 18 | 19 | //! \brief Serialize the segment to a string 20 | BufferList serialize(const uint32_t datagram_layer_checksum = 0) const; 21 | 22 | //! \name Accessors 23 | //!@{ 24 | const TCPHeader &header() const { return _header; } 25 | TCPHeader &header() { return _header; } 26 | 27 | const Buffer &payload() const { return _payload; } 28 | Buffer &payload() { return _payload; } 29 | //!@} 30 | 31 | //! \brief Segment's length in sequence space 32 | //! \note Equal to payload length plus one byte if SYN is set, plus one byte if FIN is set 33 | size_t length_in_sequence_space() const; 34 | }; 35 | 36 | #endif // SPONGE_LIBSPONGE_TCP_SEGMENT_HH 37 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tuntap_adapter.cc: -------------------------------------------------------------------------------- 1 | #include "tuntap_adapter.hh" 2 | 3 | using namespace std; 4 | 5 | //! \param[in] tap Raw network device that will be owned by the adapter 6 | //! \param[in] eth_address Ethernet address (local address) of the adapter 7 | //! \param[in] ip_address IP address (local address) of the adapter 8 | //! \param[in] next_hop IP address of the next hop (typically a router or default gateway) 9 | TCPOverIPv4OverEthernetAdapter::TCPOverIPv4OverEthernetAdapter(TapFD &&tap, 10 | const EthernetAddress ð_address, 11 | const Address &ip_address, 12 | const Address &next_hop) 13 | : _tap(move(tap)), _interface(eth_address, ip_address), _next_hop(next_hop) { 14 | // Linux seems to ignore the first frame sent on a TAP device, so send a dummy frame to prime the pump :-( 15 | EthernetFrame dummy_frame; 16 | _tap.write(dummy_frame.serialize()); 17 | } 18 | 19 | optional TCPOverIPv4OverEthernetAdapter::read() { 20 | // Read Ethernet frame from the raw device 21 | EthernetFrame frame; 22 | if (frame.parse(_tap.read()) != ParseResult::NoError) { 23 | return {}; 24 | } 25 | 26 | // Give the frame to the NetworkInterface. Get back an Internet datagram if frame was carrying one. 27 | optional ip_dgram = _interface.recv_frame(frame); 28 | 29 | // The incoming frame may have caused the NetworkInterface to send a frame. 30 | send_pending(); 31 | 32 | // Try to interpret IPv4 datagram as TCP 33 | if (ip_dgram) { 34 | return unwrap_tcp_in_ip(ip_dgram.value()); 35 | } 36 | return {}; 37 | } 38 | 39 | //! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method 40 | void TCPOverIPv4OverEthernetAdapter::tick(const size_t ms_since_last_tick) { 41 | _interface.tick(ms_since_last_tick); 42 | send_pending(); 43 | } 44 | 45 | //! \param[in] seg the TCPSegment to send 46 | void TCPOverIPv4OverEthernetAdapter::write(TCPSegment &seg) { 47 | _interface.send_datagram(wrap_tcp_in_ip(seg), _next_hop); 48 | send_pending(); 49 | } 50 | 51 | void TCPOverIPv4OverEthernetAdapter::send_pending() { 52 | while (not _interface.frames_out().empty()) { 53 | _tap.write(_interface.frames_out().front().serialize()); 54 | _interface.frames_out().pop(); 55 | } 56 | } 57 | 58 | //! Specialize LossyFdAdapter to TCPOverIPv4OverTunFdAdapter 59 | template class LossyFdAdapter; 60 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tuntap_adapter.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH 2 | #define SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH 3 | 4 | #include "ethernet_header.hh" 5 | #include "network_interface.hh" 6 | #include "tun.hh" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | //! \brief A FD adapter for IPv4 datagrams read from and written to a TUN device 13 | class TCPOverIPv4OverTunFdAdapter : public TCPOverIPv4Adapter { 14 | private: 15 | TunFD _tun; 16 | 17 | public: 18 | //! Construct from a TunFD 19 | explicit TCPOverIPv4OverTunFdAdapter(TunFD &&tun) : _tun(std::move(tun)) {} 20 | 21 | //! Attempts to read and parse an IPv4 datagram containing a TCP segment related to the current connection 22 | std::optional read() { 23 | InternetDatagram ip_dgram; 24 | if (ip_dgram.parse(_tun.read()) != ParseResult::NoError) { 25 | return {}; 26 | } 27 | return unwrap_tcp_in_ip(ip_dgram); 28 | } 29 | 30 | //! Creates an IPv4 datagram from a TCP segment and writes it to the TUN device 31 | void write(TCPSegment &seg) { _tun.write(wrap_tcp_in_ip(seg).serialize()); } 32 | 33 | //! Access the underlying TUN device 34 | operator TunFD &() { return _tun; } 35 | 36 | //! Access the underlying TUN device 37 | operator const TunFD &() const { return _tun; } 38 | }; 39 | 40 | //! Typedef for TCPOverIPv4OverTunFdAdapter 41 | using LossyTCPOverIPv4OverTunFdAdapter = LossyFdAdapter; 42 | 43 | //! \brief A FD adapter for IPv4 datagrams read from and written to a TAP device 44 | class TCPOverIPv4OverEthernetAdapter : public TCPOverIPv4Adapter { 45 | private: 46 | TapFD _tap; //!< Raw Ethernet connection 47 | 48 | NetworkInterface _interface; //!< NIC abstraction 49 | 50 | Address _next_hop; //!< IP address of the next hop 51 | 52 | void send_pending(); //!< Sends any pending Ethernet frames 53 | 54 | public: 55 | //! Construct from a TapFD 56 | explicit TCPOverIPv4OverEthernetAdapter(TapFD &&tap, 57 | const EthernetAddress ð_address, 58 | const Address &ip_address, 59 | const Address &next_hop); 60 | //! Attempts to read and parse an Ethernet frame containing an IPv4 datagram that contains a TCP segment 61 | std::optional read(); 62 | 63 | //! Sends a TCP segment (in an IPv4 datagram, in an Ethernet frame). 64 | void write(TCPSegment &seg); 65 | 66 | //! Called periodically when time elapses 67 | void tick(const size_t ms_since_last_tick); 68 | 69 | //! Access the underlying raw Ethernet connection 70 | operator TapFD &() { return _tap; } 71 | 72 | //! Access the underlying raw Ethernet connection 73 | operator const TapFD &() const { return _tap; } 74 | }; 75 | 76 | #endif // SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH 77 | -------------------------------------------------------------------------------- /libsponge/tcp_receiver.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_receiver.hh" 2 | 3 | #include 4 | 5 | // Dummy implementation of a TCP receiver 6 | 7 | // For Lab 2, please replace with a real implementation that passes the 8 | // automated checks run by `make check_lab2`. 9 | 10 | template 11 | void DUMMY_CODE(Targs &&.../* unused */) {} 12 | 13 | using namespace std; 14 | 15 | /** 16 | * \brief 当前 TCPReceiver 大体上有三种状态, 分别是 17 | * 1. LISTEN,此时 SYN 包尚未抵达。可以通过 _set_syn_flag 标志位来判断是否在当前状态 18 | * 2. SYN_RECV, 此时 SYN 抵达。只能判断当前不在 1、3状态时才能确定在当前状态 19 | * 3. FIN_RECV, 此时 FIN 抵达。可以通过 ByteStream end_input 来判断是否在当前状态 20 | */ 21 | 22 | void TCPReceiver::segment_received(const TCPSegment &seg) { 23 | // 判断是否是 SYN 包 24 | const TCPHeader &header = seg.header(); 25 | if (!_set_syn_flag) { 26 | // 注意 SYN 包之前的数据包必须全部丢弃 27 | if (!header.syn) 28 | return; 29 | _isn = header.seqno; 30 | _set_syn_flag = true; 31 | } 32 | uint64_t abs_ackno = _reassembler.stream_out().bytes_written() + 1; 33 | uint64_t curr_abs_seqno = unwrap(header.seqno, _isn, abs_ackno); 34 | 35 | //! NOTE: SYN 包中的 payload 不能被丢弃 36 | //! NOTE: reassember 足够鲁棒以至于无需进行任何 seqno 过滤操作 37 | uint64_t stream_index = curr_abs_seqno - 1 + (header.syn); 38 | _reassembler.push_substring(seg.payload().copy(), stream_index, header.fin); 39 | } 40 | 41 | optional TCPReceiver::ackno() const { 42 | // 判断是否是在 LISTEN 状态 43 | if (!_set_syn_flag) 44 | return nullopt; 45 | // 如果不在 LISTEN 状态,则 ackno 还需要加上一个 SYN 标志的长度 46 | uint64_t abs_ack_no = _reassembler.stream_out().bytes_written() + 1; 47 | // 如果当前处于 FIN_RECV 状态,则还需要加上 FIN 标志长度 48 | if (_reassembler.stream_out().input_ended()) 49 | ++abs_ack_no; 50 | return WrappingInt32(_isn) + abs_ack_no; 51 | } 52 | 53 | size_t TCPReceiver::window_size() const { return _capacity - _reassembler.stream_out().buffer_size(); } 54 | -------------------------------------------------------------------------------- /libsponge/tcp_receiver.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TCP_RECEIVER_HH 2 | #define SPONGE_LIBSPONGE_TCP_RECEIVER_HH 3 | 4 | #include "byte_stream.hh" 5 | #include "stream_reassembler.hh" 6 | #include "tcp_segment.hh" 7 | #include "wrapping_integers.hh" 8 | 9 | #include 10 | 11 | //! \brief The "receiver" part of a TCP implementation. 12 | 13 | //! Receives and reassembles segments into a ByteStream, and computes 14 | //! the acknowledgment number and window size to advertise back to the 15 | //! remote TCPSender. 16 | class TCPReceiver { 17 | WrappingInt32 _isn; 18 | bool _set_syn_flag; 19 | 20 | //! Our data structure for re-assembling bytes. 21 | StreamReassembler _reassembler; 22 | 23 | //! The maximum number of bytes we'll store. 24 | size_t _capacity; 25 | 26 | public: 27 | //! \brief Construct a TCP receiver 28 | //! 29 | //! \param capacity the maximum number of bytes that the receiver will 30 | //! store in its buffers at any give time. 31 | TCPReceiver(const size_t capacity) : _isn(0), _set_syn_flag(false), _reassembler(capacity), _capacity(capacity) {} 32 | 33 | //! \name Accessors to provide feedback to the remote TCPSender 34 | //!@{ 35 | 36 | //! \brief The ackno that should be sent to the peer 37 | //! \returns empty if no SYN has been received 38 | //! 39 | //! This is the beginning of the receiver's window, or in other words, the sequence number 40 | //! of the first byte in the stream that the receiver hasn't received. 41 | std::optional ackno() const; 42 | 43 | //! \brief The window size that should be sent to the peer 44 | //! 45 | //! Operationally: the capacity minus the number of bytes that the 46 | //! TCPReceiver is holding in its byte stream (those that have been 47 | //! reassembled, but not consumed). 48 | //! 49 | //! Formally: the difference between (a) the sequence number of 50 | //! the first byte that falls after the window (and will not be 51 | //! accepted by the receiver) and (b) the sequence number of the 52 | //! beginning of the window (the ackno). 53 | size_t window_size() const; 54 | //!@} 55 | 56 | //! \brief number of bytes stored but not yet reassembled 57 | size_t unassembled_bytes() const { return _reassembler.unassembled_bytes(); } 58 | 59 | //! \brief handle an inbound segment 60 | void segment_received(const TCPSegment &seg); 61 | 62 | //! \name "Output" interface for the reader 63 | //!@{ 64 | ByteStream &stream_out() { return _reassembler.stream_out(); } 65 | const ByteStream &stream_out() const { return _reassembler.stream_out(); } 66 | //!@} 67 | }; 68 | 69 | #endif // SPONGE_LIBSPONGE_TCP_RECEIVER_HH 70 | -------------------------------------------------------------------------------- /libsponge/util/address.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_ADDRESS_HH 2 | #define SPONGE_LIBSPONGE_ADDRESS_HH 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | //! Wrapper around [IPv4 addresses](@ref man7::ip) and DNS operations. 13 | class Address { 14 | public: 15 | //! \brief Wrapper around [sockaddr_storage](@ref man7::socket). 16 | //! \details A `sockaddr_storage` is enough space to store any socket address (IPv4 or IPv6). 17 | class Raw { 18 | public: 19 | sockaddr_storage storage{}; //!< The wrapped struct itself. 20 | operator sockaddr *(); 21 | operator const sockaddr *() const; 22 | }; 23 | 24 | private: 25 | socklen_t _size; //!< Size of the wrapped address. 26 | Raw _address{}; //!< A wrapped [sockaddr_storage](@ref man7::socket) containing the address. 27 | 28 | //! Constructor from ip/host, service/port, and hints to the resolver. 29 | Address(const std::string &node, const std::string &service, const addrinfo &hints); 30 | 31 | public: 32 | //! Construct by resolving a hostname and servicename. 33 | Address(const std::string &hostname, const std::string &service); 34 | 35 | //! Construct from dotted-quad string ("18.243.0.1") and numeric port. 36 | Address(const std::string &ip, const std::uint16_t port = 0); 37 | 38 | //! Construct from a [sockaddr *](@ref man7::socket). 39 | Address(const sockaddr *addr, const std::size_t size); 40 | 41 | //! Equality comparison. 42 | bool operator==(const Address &other) const; 43 | bool operator!=(const Address &other) const { return not operator==(other); } 44 | 45 | //! \name Conversions 46 | //!@{ 47 | 48 | //! Dotted-quad IP address string ("18.243.0.1") and numeric port. 49 | std::pair ip_port() const; 50 | //! Dotted-quad IP address string ("18.243.0.1"). 51 | std::string ip() const { return ip_port().first; } 52 | //! Numeric port (host byte order). 53 | uint16_t port() const { return ip_port().second; } 54 | //! Numeric IP address as an integer (i.e., in [host byte order](\ref man3::byteorder)). 55 | uint32_t ipv4_numeric() const; 56 | //! Create an Address from a 32-bit raw numeric IP address 57 | static Address from_ipv4_numeric(const uint32_t ip_address); 58 | //! Human-readable string, e.g., "8.8.8.8:53". 59 | std::string to_string() const; 60 | //!@} 61 | 62 | //! \name Low-level operations 63 | //!@{ 64 | 65 | //! Size of the underlying address storage. 66 | socklen_t size() const { return _size; } 67 | //! Const pointer to the underlying socket address storage. 68 | operator const sockaddr *() const { return _address; } 69 | //!@} 70 | }; 71 | 72 | //! \class Address 73 | //! For example, you can do DNS lookups: 74 | //! 75 | //! \include address_example_1.cc 76 | //! 77 | //! or you can specify an IP address and port number: 78 | //! 79 | //! \include address_example_2.cc 80 | //! 81 | //! Once you have an address, you can convert it to other useful representations, e.g., 82 | //! 83 | //! \include address_example_3.cc 84 | 85 | #endif // SPONGE_LIBSPONGE_ADDRESS_HH 86 | -------------------------------------------------------------------------------- /libsponge/util/buffer.cc: -------------------------------------------------------------------------------- 1 | #include "buffer.hh" 2 | 3 | using namespace std; 4 | 5 | void Buffer::remove_prefix(const size_t n) { 6 | if (n > str().size()) { 7 | throw out_of_range("Buffer::remove_prefix"); 8 | } 9 | _starting_offset += n; 10 | if (_storage and _starting_offset == _storage->size()) { 11 | _storage.reset(); 12 | } 13 | } 14 | 15 | void BufferList::append(const BufferList &other) { 16 | for (const auto &buf : other._buffers) { 17 | _buffers.push_back(buf); 18 | } 19 | } 20 | 21 | BufferList::operator Buffer() const { 22 | switch (_buffers.size()) { 23 | case 0: 24 | return {}; 25 | case 1: 26 | return _buffers[0]; 27 | default: { 28 | throw runtime_error( 29 | "BufferList: please use concatenate() to combine a multi-Buffer BufferList into one Buffer"); 30 | } 31 | } 32 | } 33 | 34 | string BufferList::concatenate() const { 35 | std::string ret; 36 | ret.reserve(size()); 37 | for (const auto &buf : _buffers) { 38 | ret.append(buf); 39 | } 40 | return ret; 41 | } 42 | 43 | size_t BufferList::size() const { 44 | size_t ret = 0; 45 | for (const auto &buf : _buffers) { 46 | ret += buf.size(); 47 | } 48 | return ret; 49 | } 50 | 51 | void BufferList::remove_prefix(size_t n) { 52 | while (n > 0) { 53 | if (_buffers.empty()) { 54 | throw std::out_of_range("BufferList::remove_prefix"); 55 | } 56 | 57 | if (n < _buffers.front().str().size()) { 58 | _buffers.front().remove_prefix(n); 59 | n = 0; 60 | } else { 61 | n -= _buffers.front().str().size(); 62 | _buffers.pop_front(); 63 | } 64 | } 65 | } 66 | 67 | BufferViewList::BufferViewList(const BufferList &buffers) { 68 | for (const auto &x : buffers.buffers()) { 69 | _views.push_back(x); 70 | } 71 | } 72 | 73 | void BufferViewList::remove_prefix(size_t n) { 74 | while (n > 0) { 75 | if (_views.empty()) { 76 | throw std::out_of_range("BufferListView::remove_prefix"); 77 | } 78 | 79 | if (n < _views.front().size()) { 80 | _views.front().remove_prefix(n); 81 | n = 0; 82 | } else { 83 | n -= _views.front().size(); 84 | _views.pop_front(); 85 | } 86 | } 87 | } 88 | 89 | size_t BufferViewList::size() const { 90 | size_t ret = 0; 91 | for (const auto &buf : _views) { 92 | ret += buf.size(); 93 | } 94 | return ret; 95 | } 96 | 97 | vector BufferViewList::as_iovecs() const { 98 | vector ret; 99 | ret.reserve(_views.size()); 100 | for (const auto &x : _views) { 101 | ret.push_back({const_cast(x.data()), x.size()}); 102 | } 103 | return ret; 104 | } 105 | -------------------------------------------------------------------------------- /libsponge/util/eventloop.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_EVENTLOOP_HH 2 | #define SPONGE_LIBSPONGE_EVENTLOOP_HH 3 | 4 | #include "file_descriptor.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | //! Waits for events on file descriptors and executes corresponding callbacks. 12 | class EventLoop { 13 | public: 14 | //! Indicates interest in reading (In) or writing (Out) a polled fd. 15 | enum class Direction : short { 16 | In = POLLIN, //!< Callback will be triggered when Rule::fd is readable. 17 | Out = POLLOUT //!< Callback will be triggered when Rule::fd is writable. 18 | }; 19 | 20 | private: 21 | using CallbackT = std::function; //!< Callback for ready Rule::fd 22 | using InterestT = std::function; //!< `true` return indicates Rule::fd should be polled. 23 | 24 | //! \brief Specifies a condition and callback that an EventLoop should handle. 25 | //! \details Created by calling EventLoop::add_rule() or EventLoop::add_cancelable_rule(). 26 | class Rule { 27 | public: 28 | FileDescriptor fd; //!< FileDescriptor to monitor for activity. 29 | Direction direction; //!< Direction::In for reading from fd, Direction::Out for writing to fd. 30 | CallbackT callback; //!< A callback that reads or writes fd. 31 | InterestT interest; //!< A callback that returns `true` whenever fd should be polled. 32 | CallbackT cancel; //!< A callback that is called when the rule is cancelled (e.g. on hangup) 33 | 34 | //! Returns the number of times fd has been read or written, depending on the value of Rule::direction. 35 | //! \details This function is used internally by EventLoop; you will not need to call it 36 | unsigned int service_count() const; 37 | }; 38 | 39 | std::list _rules{}; //!< All rules that have been added and not canceled. 40 | 41 | public: 42 | //! Returned by each call to EventLoop::wait_next_event. 43 | enum class Result { 44 | Success, //!< At least one Rule was triggered. 45 | Timeout, //!< No rules were triggered before timeout. 46 | Exit //!< All rules have been canceled or were uninterested; make no further calls to EventLoop::wait_next_event. 47 | }; 48 | 49 | //! Add a rule whose callback will be called when `fd` is ready in the specified Direction. 50 | void add_rule( 51 | const FileDescriptor &fd, 52 | const Direction direction, 53 | const CallbackT &callback, 54 | const InterestT &interest = [] { return true; }, 55 | const CallbackT &cancel = [] {}); 56 | 57 | //! Calls [poll(2)](\ref man2::poll) and then executes callback for each ready fd. 58 | Result wait_next_event(const int timeout_ms); 59 | }; 60 | 61 | using Direction = EventLoop::Direction; 62 | 63 | //! \class EventLoop 64 | //! 65 | //! An EventLoop holds a std::list of Rule objects. Each time EventLoop::wait_next_event is 66 | //! executed, the EventLoop uses the Rule objects to construct a call to [poll(2)](\ref man2::poll). 67 | //! 68 | //! When a Rule is installed using EventLoop::add_rule, it will be polled for the specified Rule::direction 69 | //! whenver the Rule::interest callback returns `true`, until Rule::fd is no longer readable 70 | //! (for Rule::direction == Direction::In) or writable (for Rule::direction == Direction::Out). 71 | //! Once this occurs, the Rule is canceled, i.e., the EventLoop deletes it. 72 | //! 73 | //! A Rule installed using EventLoop::add_cancelable_rule will be polled and canceled under the 74 | //! same conditions, with the additional condition that if Rule::callback returns `true`, the 75 | //! Rule will be canceled. 76 | 77 | #endif // SPONGE_LIBSPONGE_EVENTLOOP_HH 78 | -------------------------------------------------------------------------------- /libsponge/util/file_descriptor.cc: -------------------------------------------------------------------------------- 1 | #include "file_descriptor.hh" 2 | 3 | #include "util.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | //! \param[in] fd is the file descriptor number returned by [open(2)](\ref man2::open) or similar 15 | FileDescriptor::FDWrapper::FDWrapper(const int fd) : _fd(fd) { 16 | if (fd < 0) { 17 | throw runtime_error("invalid fd number:" + to_string(fd)); 18 | } 19 | } 20 | 21 | void FileDescriptor::FDWrapper::close() { 22 | SystemCall("close", ::close(_fd)); 23 | _eof = _closed = true; 24 | } 25 | 26 | FileDescriptor::FDWrapper::~FDWrapper() { 27 | try { 28 | if (_closed) { 29 | return; 30 | } 31 | close(); 32 | } catch (const exception &e) { 33 | // don't throw an exception from the destructor 34 | std::cerr << "Exception destructing FDWrapper: " << e.what() << std::endl; 35 | } 36 | } 37 | 38 | //! \param[in] fd is the file descriptor number returned by [open(2)](\ref man2::open) or similar 39 | FileDescriptor::FileDescriptor(const int fd) : _internal_fd(make_shared(fd)) {} 40 | 41 | //! Private constructor used by duplicate() 42 | FileDescriptor::FileDescriptor(shared_ptr other_shared_ptr) : _internal_fd(move(other_shared_ptr)) {} 43 | 44 | //! \returns a copy of this FileDescriptor 45 | FileDescriptor FileDescriptor::duplicate() const { return FileDescriptor(_internal_fd); } 46 | 47 | //! \param[in] limit is the maximum number of bytes to read; fewer bytes may be returned 48 | //! \param[out] str is the string to be read 49 | void FileDescriptor::read(std::string &str, const size_t limit) { 50 | constexpr size_t BUFFER_SIZE = 1024 * 1024; // maximum size of a read 51 | const size_t size_to_read = min(BUFFER_SIZE, limit); 52 | str.resize(size_to_read); 53 | 54 | ssize_t bytes_read = SystemCall("read", ::read(fd_num(), str.data(), size_to_read)); 55 | if (limit > 0 && bytes_read == 0) { 56 | _internal_fd->_eof = true; 57 | } 58 | if (bytes_read > static_cast(size_to_read)) { 59 | throw runtime_error("read() read more than requested"); 60 | } 61 | str.resize(bytes_read); 62 | 63 | register_read(); 64 | } 65 | 66 | //! \param[in] limit is the maximum number of bytes to read; fewer bytes may be returned 67 | //! \returns a vector of bytes read 68 | string FileDescriptor::read(const size_t limit) { 69 | string ret; 70 | 71 | read(ret, limit); 72 | 73 | return ret; 74 | } 75 | 76 | size_t FileDescriptor::write(BufferViewList buffer, const bool write_all) { 77 | size_t total_bytes_written = 0; 78 | 79 | do { 80 | auto iovecs = buffer.as_iovecs(); 81 | 82 | const ssize_t bytes_written = SystemCall("writev", ::writev(fd_num(), iovecs.data(), iovecs.size())); 83 | if (bytes_written == 0 and buffer.size() != 0) { 84 | throw runtime_error("write returned 0 given non-empty input buffer"); 85 | } 86 | 87 | if (bytes_written > ssize_t(buffer.size())) { 88 | throw runtime_error("write wrote more than length of input buffer"); 89 | } 90 | 91 | register_write(); 92 | 93 | buffer.remove_prefix(bytes_written); 94 | 95 | total_bytes_written += bytes_written; 96 | } while (write_all and buffer.size()); 97 | 98 | return total_bytes_written; 99 | } 100 | 101 | void FileDescriptor::set_blocking(const bool blocking_state) { 102 | int flags = SystemCall("fcntl", fcntl(fd_num(), F_GETFL)); 103 | if (blocking_state) { 104 | flags ^= (flags & O_NONBLOCK); 105 | } else { 106 | flags |= O_NONBLOCK; 107 | } 108 | 109 | SystemCall("fcntl", fcntl(fd_num(), F_SETFL, flags)); 110 | } 111 | -------------------------------------------------------------------------------- /libsponge/util/parser.cc: -------------------------------------------------------------------------------- 1 | #include "parser.hh" 2 | 3 | using namespace std; 4 | 5 | //! \param[in] r is the ParseResult to show 6 | //! \returns a string representation of the ParseResult 7 | string as_string(const ParseResult r) { 8 | static constexpr const char *_names[] = { 9 | "NoError", 10 | "BadChecksum", 11 | "PacketTooShort", 12 | "WrongIPVersion", 13 | "HeaderTooShort", 14 | "TruncatedPacket", 15 | }; 16 | 17 | return _names[static_cast(r)]; 18 | } 19 | 20 | void NetParser::_check_size(const size_t size) { 21 | if (size > _buffer.size()) { 22 | set_error(ParseResult::PacketTooShort); 23 | } 24 | } 25 | 26 | template 27 | T NetParser::_parse_int() { 28 | constexpr size_t len = sizeof(T); 29 | _check_size(len); 30 | if (error()) { 31 | return 0; 32 | } 33 | 34 | T ret = 0; 35 | for (size_t i = 0; i < len; i++) { 36 | ret <<= 8; 37 | ret += uint8_t(_buffer.at(i)); 38 | } 39 | 40 | _buffer.remove_prefix(len); 41 | 42 | return ret; 43 | } 44 | 45 | void NetParser::remove_prefix(const size_t n) { 46 | _check_size(n); 47 | if (error()) { 48 | return; 49 | } 50 | _buffer.remove_prefix(n); 51 | } 52 | 53 | template 54 | void NetUnparser::_unparse_int(string &s, T val) { 55 | constexpr size_t len = sizeof(T); 56 | for (size_t i = 0; i < len; ++i) { 57 | const uint8_t the_byte = (val >> ((len - i - 1) * 8)) & 0xff; 58 | s.push_back(the_byte); 59 | } 60 | } 61 | 62 | uint32_t NetParser::u32() { return _parse_int(); } 63 | 64 | uint16_t NetParser::u16() { return _parse_int(); } 65 | 66 | uint8_t NetParser::u8() { return _parse_int(); } 67 | 68 | void NetUnparser::u32(string &s, const uint32_t val) { return _unparse_int(s, val); } 69 | 70 | void NetUnparser::u16(string &s, const uint16_t val) { return _unparse_int(s, val); } 71 | 72 | void NetUnparser::u8(string &s, const uint8_t val) { return _unparse_int(s, val); } 73 | -------------------------------------------------------------------------------- /libsponge/util/parser.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_PARSER_HH 2 | #define SPONGE_LIBSPONGE_PARSER_HH 3 | 4 | #include "buffer.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | //! The result of parsing or unparsing an IP datagram, TCP segment, Ethernet frame, or ARP message 12 | enum class ParseResult { 13 | NoError = 0, //!< Success 14 | BadChecksum, //!< Bad checksum 15 | PacketTooShort, //!< Not enough data to finish parsing 16 | WrongIPVersion, //!< Got a version of IP other than 4 17 | HeaderTooShort, //!< Header length is shorter than minimum required 18 | TruncatedPacket, //!< Packet length is shorter than header claims 19 | Unsupported //!< Packet uses unsupported features 20 | }; 21 | 22 | //! Output a string representation of a ParseResult 23 | std::string as_string(const ParseResult r); 24 | 25 | class NetParser { 26 | private: 27 | Buffer _buffer; 28 | ParseResult _error = ParseResult::NoError; //!< Result of parsing so far 29 | 30 | //! Check that there is sufficient data to parse the next token 31 | void _check_size(const size_t size); 32 | 33 | //! Generic integer parsing method (used by u32, u16, u8) 34 | template 35 | T _parse_int(); 36 | 37 | public: 38 | NetParser(Buffer buffer) : _buffer(buffer) {} 39 | 40 | Buffer buffer() const { return _buffer; } 41 | 42 | //! Get the current value stored in BaseParser::_error 43 | ParseResult get_error() const { return _error; } 44 | 45 | //! \brief Set BaseParser::_error 46 | //! \param[in] res is the value to store in BaseParser::_error 47 | void set_error(ParseResult res) { _error = res; } 48 | 49 | //! Returns `true` if there has been an error 50 | bool error() const { return get_error() != ParseResult::NoError; } 51 | 52 | //! Parse a 32-bit integer in network byte order from the data stream 53 | uint32_t u32(); 54 | 55 | //! Parse a 16-bit integer in network byte order from the data stream 56 | uint16_t u16(); 57 | 58 | //! Parse an 8-bit integer in network byte order from the data stream 59 | uint8_t u8(); 60 | 61 | //! Remove n bytes from the buffer 62 | void remove_prefix(const size_t n); 63 | }; 64 | 65 | struct NetUnparser { 66 | template 67 | static void _unparse_int(std::string &s, T val); 68 | 69 | //! Write a 32-bit integer into the data stream in network byte order 70 | static void u32(std::string &s, const uint32_t val); 71 | 72 | //! Write a 16-bit integer into the data stream in network byte order 73 | static void u16(std::string &s, const uint16_t val); 74 | 75 | //! Write an 8-bit integer into the data stream in network byte order 76 | static void u8(std::string &s, const uint8_t val); 77 | }; 78 | 79 | #endif // SPONGE_LIBSPONGE_PARSER_HH 80 | -------------------------------------------------------------------------------- /libsponge/util/tun.cc: -------------------------------------------------------------------------------- 1 | #include "tun.hh" 2 | 3 | #include "util.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static constexpr const char *CLONEDEV = "/dev/net/tun"; 12 | 13 | using namespace std; 14 | 15 | //! \param[in] devname is the name of the TUN or TAP device, specified at its creation. 16 | //! \param[in] is_tun is `true` for a TUN device (expects IP datagrams), or `false` for a TAP device (expects Ethernet frames) 17 | //! 18 | //! To create a TUN device, you should already have run 19 | //! 20 | //! ip tuntap add mode tun user `username` name `devname` 21 | //! 22 | //! as root before calling this function. 23 | 24 | TunTapFD::TunTapFD(const string &devname, const bool is_tun) 25 | : FileDescriptor(SystemCall("open", open(CLONEDEV, O_RDWR))) { 26 | struct ifreq tun_req {}; 27 | 28 | tun_req.ifr_flags = (is_tun ? IFF_TUN : IFF_TAP) | IFF_NO_PI; // tun device with no packetinfo 29 | 30 | // copy devname to ifr_name, making sure to null terminate 31 | 32 | strncpy(static_cast(tun_req.ifr_name), devname.data(), IFNAMSIZ - 1); 33 | tun_req.ifr_name[IFNAMSIZ - 1] = '\0'; 34 | 35 | SystemCall("ioctl", ioctl(fd_num(), TUNSETIFF, static_cast(&tun_req))); 36 | } 37 | -------------------------------------------------------------------------------- /libsponge/util/tun.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TUN_HH 2 | #define SPONGE_LIBSPONGE_TUN_HH 3 | 4 | #include "file_descriptor.hh" 5 | 6 | #include 7 | 8 | //! A FileDescriptor to a [Linux TUN/TAP](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) device 9 | class TunTapFD : public FileDescriptor { 10 | public: 11 | //! Open an existing persistent [TUN or TAP device](https://www.kernel.org/doc/Documentation/networking/tuntap.txt). 12 | explicit TunTapFD(const std::string &devname, const bool is_tun); 13 | }; 14 | 15 | //! A FileDescriptor to a [Linux TUN](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) device 16 | class TunFD : public TunTapFD { 17 | public: 18 | //! Open an existing persistent [TUN device](https://www.kernel.org/doc/Documentation/networking/tuntap.txt). 19 | explicit TunFD(const std::string &devname) : TunTapFD(devname, true) {} 20 | }; 21 | 22 | //! A FileDescriptor to a [Linux TAP](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) device 23 | class TapFD : public TunTapFD { 24 | public: 25 | //! Open an existing persistent [TAP device](https://www.kernel.org/doc/Documentation/networking/tuntap.txt). 26 | explicit TapFD(const std::string &devname) : TunTapFD(devname, false) {} 27 | }; 28 | 29 | #endif // SPONGE_LIBSPONGE_TUN_HH 30 | -------------------------------------------------------------------------------- /libsponge/util/util.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_UTIL_HH 2 | #define SPONGE_LIBSPONGE_UTIL_HH 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | //! std::system_error plus the name of what was being attempted 16 | class tagged_error : public std::system_error { 17 | private: 18 | std::string _attempt_and_error; //!< What was attempted, and what happened 19 | 20 | public: 21 | //! \brief Construct from a category, an attempt, and an error code 22 | //! \param[in] category is the category of error 23 | //! \param[in] attempt is what was supposed to happen 24 | //! \param[in] error_code is the resulting error 25 | tagged_error(const std::error_category &category, const std::string &attempt, const int error_code) 26 | : system_error(error_code, category), _attempt_and_error(attempt + ": " + std::system_error::what()) {} 27 | 28 | //! Returns a C string describing the error 29 | const char *what() const noexcept override { return _attempt_and_error.c_str(); } 30 | }; 31 | 32 | //! a tagged_error for syscalls 33 | class unix_error : public tagged_error { 34 | public: 35 | //! brief Construct from a syscall name and the resulting errno 36 | //! \param[in] attempt is the name of the syscall attempted 37 | //! \param[in] error is the [errno(3)](\ref man3::errno) that resulted 38 | explicit unix_error(const std::string &attempt, const int error = errno) 39 | : tagged_error(std::system_category(), attempt, error) {} 40 | }; 41 | 42 | //! Error-checking wrapper for most syscalls 43 | int SystemCall(const char *attempt, const int return_value, const int errno_mask = 0); 44 | 45 | //! Version of SystemCall that takes a C++ std::string 46 | int SystemCall(const std::string &attempt, const int return_value, const int errno_mask = 0); 47 | 48 | //! Seed a fast random generator 49 | std::mt19937 get_random_generator(); 50 | 51 | //! Get the time in milliseconds since the program began. 52 | uint64_t timestamp_ms(); 53 | 54 | //! The internet checksum algorithm 55 | class InternetChecksum { 56 | private: 57 | uint32_t _sum; 58 | bool _parity{}; 59 | 60 | public: 61 | InternetChecksum(const uint32_t initial_sum = 0); 62 | void add(std::string_view data); 63 | uint16_t value() const; 64 | }; 65 | 66 | //! Hexdump the contents of a packet (or any other sequence of bytes) 67 | void hexdump(const char *data, const size_t len, const size_t indent = 0); 68 | 69 | //! Hexdump the contents of a packet (or any other sequence of bytes) 70 | void hexdump(const uint8_t *data, const size_t len, const size_t indent = 0); 71 | 72 | #endif // SPONGE_LIBSPONGE_UTIL_HH 73 | -------------------------------------------------------------------------------- /libsponge/wrapping_integers.cc: -------------------------------------------------------------------------------- 1 | #include "wrapping_integers.hh" 2 | 3 | // Dummy implementation of a 32-bit wrapping integer 4 | 5 | // For Lab 2, please replace with a real implementation that passes the 6 | // automated checks run by `make check_lab2`. 7 | 8 | template 9 | void DUMMY_CODE(Targs &&.../* unused */) {} 10 | 11 | using namespace std; 12 | 13 | //! Transform an "absolute" 64-bit sequence number (zero-indexed) into a WrappingInt32 14 | //! \param n The input absolute 64-bit sequence number 15 | //! \param isn The initial sequence number 16 | WrappingInt32 wrap(uint64_t n, WrappingInt32 isn) { return WrappingInt32{isn + static_cast(n)}; } 17 | 18 | //! Transform a WrappingInt32 into an "absolute" 64-bit sequence number (zero-indexed) 19 | //! \param n The relative sequence number 20 | //! \param isn The initial sequence number 21 | //! \param checkpoint A recent absolute 64-bit sequence number 22 | //! \returns the 64-bit sequence number that wraps to `n` and is closest to `checkpoint` 23 | //! 24 | //! \note Each of the two streams of the TCP connection has its own ISN. One stream 25 | //! runs from the local TCPSender to the remote TCPReceiver and has one ISN, 26 | //! and the other stream runs from the remote TCPSender to the local TCPReceiver and 27 | //! has a different ISN. 28 | uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) { 29 | // 32位的范围 30 | const constexpr uint64_t INT32_RANGE = 1l << 32; 31 | // 获取 n 与 isn 之间的偏移量(mod) 32 | // 实际的 absolute seqno % INT32_RANGE == offset 33 | uint32_t offset = n - isn; 34 | /// NOTE: 最大的坑点!如果 checkpoint 比 offset 大,那么就需要进行四舍五入 35 | /// NOTE: 但是!!! 如果 checkpoint 比 offset 还小,那就只能向上入了,即此时的 offset 就是 abs seqno 36 | if (checkpoint > offset) { 37 | // 加上半个 INT32_RANGE 是为了四舍五入 38 | uint64_t real_checkpoint = (checkpoint - offset) + (INT32_RANGE >> 1); 39 | uint64_t wrap_num = real_checkpoint / INT32_RANGE; 40 | return wrap_num * INT32_RANGE + offset; 41 | } else 42 | return offset; 43 | } 44 | -------------------------------------------------------------------------------- /libsponge/wrapping_integers.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH 2 | #define SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH 3 | 4 | #include 5 | #include 6 | 7 | //! \brief A 32-bit integer, expressed relative to an arbitrary initial sequence number (ISN) 8 | //! \note This is used to express TCP sequence numbers (seqno) and acknowledgment numbers (ackno) 9 | class WrappingInt32 { 10 | private: 11 | uint32_t _raw_value; //!< The raw 32-bit stored integer 12 | 13 | public: 14 | //! Construct from a raw 32-bit unsigned integer 15 | explicit WrappingInt32(uint32_t raw_value) : _raw_value(raw_value) {} 16 | 17 | uint32_t raw_value() const { return _raw_value; } //!< Access raw stored value 18 | }; 19 | 20 | //! Transform a 64-bit absolute sequence number (zero-indexed) into a 32-bit relative sequence number 21 | //! \param n the absolute sequence number 22 | //! \param isn the initial sequence number 23 | //! \returns the relative sequence number 24 | WrappingInt32 wrap(uint64_t n, WrappingInt32 isn); 25 | 26 | //! Transform a 32-bit relative sequence number into a 64-bit absolute sequence number (zero-indexed) 27 | //! \param n The relative sequence number 28 | //! \param isn The initial sequence number 29 | //! \param checkpoint A recent absolute sequence number 30 | //! \returns the absolute sequence number that wraps to `n` and is closest to `checkpoint` 31 | //! 32 | //! \note Each of the two streams of the TCP connection has its own ISN. One stream 33 | //! runs from the local TCPSender to the remote TCPReceiver and has one ISN, 34 | //! and the other stream runs from the remote TCPSender to the local TCPReceiver and 35 | //! has a different ISN. 36 | uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint); 37 | 38 | //! \name Helper functions 39 | //!@{ 40 | 41 | //! \brief The offset of `a` relative to `b` 42 | //! \param b the starting point 43 | //! \param a the ending point 44 | //! \returns the number of increments needed to get from `b` to `a`, 45 | //! negative if the number of decrements needed is less than or equal to 46 | //! the number of increments 47 | inline int32_t operator-(WrappingInt32 a, WrappingInt32 b) { return a.raw_value() - b.raw_value(); } 48 | 49 | //! \brief Whether the two integers are equal. 50 | inline bool operator==(WrappingInt32 a, WrappingInt32 b) { return a.raw_value() == b.raw_value(); } 51 | 52 | //! \brief Whether the two integers are not equal. 53 | inline bool operator!=(WrappingInt32 a, WrappingInt32 b) { return !(a == b); } 54 | 55 | //! \brief Serializes the wrapping integer, `a`. 56 | inline std::ostream &operator<<(std::ostream &os, WrappingInt32 a) { return os << a.raw_value(); } 57 | 58 | //! \brief The point `b` steps past `a`. 59 | inline WrappingInt32 operator+(WrappingInt32 a, uint32_t b) { return WrappingInt32{a.raw_value() + b}; } 60 | 61 | //! \brief The point `b` steps before `a`. 62 | inline WrappingInt32 operator-(WrappingInt32 a, uint32_t b) { return a + -b; } 63 | //!@} 64 | 65 | #endif // SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH 66 | -------------------------------------------------------------------------------- /tap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | show_usage () { 4 | echo "Usage: $0 [tapnum ...]" 5 | exit 1 6 | } 7 | 8 | start_tap () { 9 | local TAPNUM="$1" TAPDEV="tap$1" LLADDR="02:B0:1D:FA:CE:"`printf "%02x" $1` 10 | ip tuntap add mode tap user "${SUDO_USER}" name "${TAPDEV}" 11 | ip link set "${TAPDEV}" address "${LLADDR}" 12 | 13 | ip addr add "${TUN_IP_PREFIX}.${TAPNUM}.1/24" dev "${TAPDEV}" 14 | ip link set dev "${TAPDEV}" up 15 | ip route change "${TUN_IP_PREFIX}.${TAPNUM}.0/24" dev "${TAPDEV}" rto_min 10ms 16 | 17 | # Apply NAT (masquerading) only to traffic from CS144's network devices 18 | iptables -t nat -A PREROUTING -s ${TUN_IP_PREFIX}.${TAPNUM}.0/24 -j CONNMARK --set-mark ${TAPNUM} 19 | iptables -t nat -A POSTROUTING -j MASQUERADE -m connmark --mark ${TAPNUM} 20 | echo 1 > /proc/sys/net/ipv4/ip_forward 21 | } 22 | 23 | stop_tap () { 24 | local TAPDEV="tap$1" 25 | iptables -t nat -D PREROUTING -s ${TUN_IP_PREFIX}.${1}.0/24 -j CONNMARK --set-mark ${1} 26 | iptables -t nat -D POSTROUTING -j MASQUERADE -m connmark --mark ${1} 27 | ip tuntap del mode tap name "$TAPDEV" 28 | } 29 | 30 | start_all () { 31 | while [ ! -z "$1" ]; do 32 | local INTF="$1"; shift 33 | start_tap "$INTF" 34 | done 35 | } 36 | 37 | stop_all () { 38 | while [ ! -z "$1" ]; do 39 | local INTF="$1"; shift 40 | stop_tap "$INTF" 41 | done 42 | } 43 | 44 | restart_all() { 45 | stop_all "$@" 46 | start_all "$@" 47 | } 48 | 49 | check_tap () { 50 | [ "$#" != 1 ] && { echo "bad params in check_tap"; exit 1; } 51 | local TAPDEV="tap${1}" 52 | # make sure tap is healthy: device is up, ip_forward is set, and iptables is configured 53 | ip link show ${TAPDEV} &>/dev/null || return 1 54 | [ "$(cat /proc/sys/net/ipv4/ip_forward)" = "1" ] || return 2 55 | } 56 | 57 | check_sudo () { 58 | if [ "$SUDO_USER" = "root" ]; then 59 | echo "please execute this script as a regular user, not as root" 60 | exit 1 61 | fi 62 | if [ -z "$SUDO_USER" ]; then 63 | # if the user didn't call us with sudo, re-execute 64 | exec sudo $0 "$MODE" "$@" 65 | fi 66 | } 67 | 68 | # check arguments 69 | if [ -z "$1" ] || ([ "$1" != "start" ] && [ "$1" != "stop" ] && [ "$1" != "restart" ] && [ "$1" != "check" ]); then 70 | show_usage 71 | fi 72 | MODE=$1; shift 73 | 74 | # set default argument 75 | if [ "$#" = "0" ]; then 76 | set -- 10 77 | fi 78 | 79 | # execute 'check' before trying to sudo 80 | # - like start, but exit successfully if everything is OK 81 | if [ "$MODE" = "check" ]; then 82 | declare -a INTFS 83 | MODE="start" 84 | while [ ! -z "$1" ]; do 85 | INTF="$1"; shift 86 | check_tap ${INTF} 87 | RET=$? 88 | if [ "$RET" = "0" ]; then 89 | continue 90 | fi 91 | 92 | if [ "$((RET > 1))" = "1" ]; then 93 | MODE="restart" 94 | fi 95 | INTFS+=($INTF) 96 | done 97 | 98 | # address only the interfaces that need it 99 | set -- "${INTFS[@]}" 100 | if [ "$#" = "0" ]; then 101 | exit 0 102 | fi 103 | echo -e "[$0] Bringing up tunnels ${INTFS[@]}:" 104 | fi 105 | 106 | # sudo if necessary 107 | check_sudo "$@" 108 | 109 | # get configuration 110 | . "$(dirname "$0")"/etc/tunconfig 111 | 112 | # start, stop, or restart all intfs 113 | eval "${MODE}_all" "$@" 114 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library (spongechecks STATIC send_equivalence_checker.cc tcp_fsm_test_harness.cc byte_stream_test_harness.cc network_interface_test_harness.cc) 2 | 3 | macro (add_test_exec exec_name) 4 | add_executable ("${exec_name}" "${exec_name}.cc") 5 | target_link_libraries ("${exec_name}" spongechecks ${ARGN}) 6 | target_link_libraries ("${exec_name}" sponge ${ARGN}) 7 | endmacro (add_test_exec) 8 | 9 | add_test_exec (tcp_parser ${LIBPCAP}) 10 | add_test_exec (ipv4_parser ${LIBPCAP}) 11 | add_test_exec (fsm_active_close) 12 | add_test_exec (fsm_passive_close) 13 | add_test_exec (fsm_ack_rst_relaxed) 14 | add_test_exec (fsm_ack_rst_win_relaxed) 15 | add_test_exec (fsm_stream_reassembler_cap) 16 | add_test_exec (fsm_stream_reassembler_single) 17 | add_test_exec (fsm_stream_reassembler_seq) 18 | add_test_exec (fsm_stream_reassembler_dup) 19 | add_test_exec (fsm_stream_reassembler_holes) 20 | add_test_exec (fsm_stream_reassembler_many) 21 | add_test_exec (fsm_stream_reassembler_overlapping) 22 | add_test_exec (fsm_stream_reassembler_win) 23 | add_test_exec (fsm_connect_relaxed) 24 | add_test_exec (fsm_listen_relaxed) 25 | add_test_exec (fsm_reorder) 26 | add_test_exec (fsm_loopback) 27 | add_test_exec (fsm_loopback_win) 28 | add_test_exec (fsm_retx_relaxed) 29 | add_test_exec (fsm_retx_win) 30 | add_test_exec (fsm_winsize) 31 | add_test_exec (wrapping_integers_cmp) 32 | add_test_exec (wrapping_integers_unwrap) 33 | add_test_exec (wrapping_integers_wrap) 34 | add_test_exec (wrapping_integers_roundtrip) 35 | add_test_exec (byte_stream_construction) 36 | add_test_exec (byte_stream_one_write) 37 | add_test_exec (byte_stream_two_writes) 38 | add_test_exec (byte_stream_capacity) 39 | add_test_exec (byte_stream_many_writes) 40 | add_test_exec (recv_connect) 41 | add_test_exec (recv_transmit) 42 | add_test_exec (recv_window) 43 | add_test_exec (recv_reorder) 44 | add_test_exec (recv_close) 45 | add_test_exec (recv_special) 46 | add_test_exec (send_connect) 47 | add_test_exec (send_transmit) 48 | add_test_exec (send_retx) 49 | add_test_exec (send_ack) 50 | add_test_exec (send_window) 51 | add_test_exec (send_close) 52 | add_test_exec (send_extra) 53 | add_test_exec (net_interface) 54 | -------------------------------------------------------------------------------- /tests/byte_stream_construction.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "byte_stream_test_harness.hh" 3 | 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | int main() { 10 | try { 11 | { 12 | ByteStreamTestHarness test{"construction", 15}; 13 | test.execute(InputEnded{false}); 14 | test.execute(BufferEmpty{true}); 15 | test.execute(Eof{false}); 16 | test.execute(BytesRead{0}); 17 | test.execute(BytesWritten{0}); 18 | test.execute(RemainingCapacity{15}); 19 | test.execute(BufferSize{0}); 20 | } 21 | 22 | { 23 | ByteStreamTestHarness test{"construction-end", 15}; 24 | test.execute(EndInput{}); 25 | test.execute(InputEnded{true}); 26 | test.execute(BufferEmpty{true}); 27 | test.execute(Eof{true}); 28 | test.execute(BytesRead{0}); 29 | test.execute(BytesWritten{0}); 30 | test.execute(RemainingCapacity{15}); 31 | test.execute(BufferSize{0}); 32 | } 33 | 34 | } catch (const exception &e) { 35 | cerr << "Exception: " << e.what() << endl; 36 | return EXIT_FAILURE; 37 | } 38 | 39 | return EXIT_SUCCESS; 40 | } 41 | -------------------------------------------------------------------------------- /tests/byte_stream_many_writes.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "byte_stream_test_harness.hh" 3 | #include "util.hh" 4 | 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | int main() { 11 | try { 12 | auto rd = get_random_generator(); 13 | const size_t NREPS = 1000; 14 | const size_t MIN_WRITE = 10; 15 | const size_t MAX_WRITE = 200; 16 | const size_t CAPACITY = MAX_WRITE * NREPS; 17 | 18 | { 19 | ByteStreamTestHarness test{"many writes", CAPACITY}; 20 | 21 | size_t acc = 0; 22 | for (size_t i = 0; i < NREPS; ++i) { 23 | const size_t size = MIN_WRITE + (rd() % (MAX_WRITE - MIN_WRITE)); 24 | string d(size, 0); 25 | generate(d.begin(), d.end(), [&] { return 'a' + (rd() % 26); }); 26 | 27 | test.execute(Write{d}.with_bytes_written(size)); 28 | acc += size; 29 | 30 | test.execute(InputEnded{false}); 31 | test.execute(BufferEmpty{false}); 32 | test.execute(Eof{false}); 33 | test.execute(BytesRead{0}); 34 | test.execute(BytesWritten{acc}); 35 | test.execute(RemainingCapacity{CAPACITY - acc}); 36 | test.execute(BufferSize{acc}); 37 | } 38 | } 39 | 40 | } catch (const exception &e) { 41 | cerr << "Exception: " << e.what() << endl; 42 | return EXIT_FAILURE; 43 | } 44 | 45 | return EXIT_SUCCESS; 46 | } 47 | -------------------------------------------------------------------------------- /tests/fsm_ack_rst_win.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | using State = TCPTestHarness::State; 14 | 15 | int main() { 16 | try { 17 | TCPConfig cfg{}; 18 | const WrappingInt32 base_seq(1 << 31); 19 | 20 | // test #1: in ESTABLISHED, send unacceptable segments and ACKs 21 | { 22 | TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, base_seq - 1, base_seq - 1); 23 | 24 | // acceptable ack---no response 25 | test_1.send_ack(base_seq, base_seq); 26 | 27 | test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after acceptable ACK"); 28 | 29 | // ack in the past---no response 30 | test_1.send_ack(base_seq, base_seq - 1); 31 | 32 | test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after past ACK"); 33 | 34 | // ack in the future---should get ACK back 35 | test_1.send_ack(base_seq, base_seq + 1); 36 | 37 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: bad ACK after future ACK"); 38 | 39 | // segment out of the window---should get an ACK 40 | test_1.send_byte(base_seq - 1, base_seq, 1); 41 | 42 | test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on early seqno"); 43 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on early seqno"); 44 | 45 | // segment out of the window---should get an ACK 46 | test_1.send_byte(base_seq + cfg.recv_capacity, base_seq, 1); 47 | 48 | test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on late seqno"); 49 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on late seqno"); 50 | 51 | // segment in the window but late---should get an ACK and seg should be queued 52 | test_1.send_byte(base_seq + cfg.recv_capacity - 1, base_seq, 1); 53 | 54 | test_1.execute(ExpectUnassembledBytes{1}, "seg not queued on end-of-window seqno"); 55 | 56 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on end-of-window seqno"); 57 | test_1.execute(ExpectNoData{}, "test 1 failed: no ack on end-of-window seqno"); 58 | 59 | // segment next byte in the window - ack should advance and data should be readable 60 | test_1.send_byte(base_seq, base_seq, 1); 61 | 62 | test_1.execute(ExpectUnassembledBytes{1}, "seg not processed on next seqno"); 63 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq + 1), "test 1 failed: no ack on next seqno"); 64 | test_1.execute(ExpectData{}, "test 1 failed: no ack on next seqno"); 65 | 66 | // segment not in window with RST set --- should get nothing back 67 | test_1.send_rst(base_seq); 68 | test_1.send_rst(base_seq + cfg.recv_capacity + 1); 69 | test_1.execute(ExpectNoSegment{}, "test 1 failed: got a response to an out-of-window RST"); 70 | 71 | test_1.send_rst(base_seq + 1); 72 | test_1.execute(ExpectState{State::RESET}); 73 | } 74 | } catch (const exception &e) { 75 | cerr << e.what() << endl; 76 | return EXIT_FAILURE; 77 | } 78 | 79 | return EXIT_SUCCESS; 80 | } 81 | -------------------------------------------------------------------------------- /tests/fsm_ack_rst_win_relaxed.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | using State = TCPTestHarness::State; 14 | 15 | int main() { 16 | try { 17 | TCPConfig cfg{}; 18 | const WrappingInt32 base_seq(1 << 31); 19 | 20 | // test #1: in ESTABLISHED, send unacceptable segments and ACKs 21 | { 22 | TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, base_seq - 1, base_seq - 1); 23 | 24 | // acceptable ack---no response 25 | test_1.send_ack(base_seq, base_seq); 26 | 27 | test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after acceptable ACK"); 28 | 29 | // ack in the past---no response 30 | test_1.send_ack(base_seq, base_seq - 1); 31 | 32 | test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after past ACK"); 33 | 34 | /* remove corrective ACKs 35 | // ack in the future---should get ACK back 36 | test_1.send_ack(base_seq, base_seq + 1); 37 | 38 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: bad ACK after future ACK"); 39 | */ 40 | 41 | // segment out of the window---should get an ACK 42 | test_1.send_byte(base_seq - 1, base_seq, 1); 43 | 44 | test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on early seqno"); 45 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on early seqno"); 46 | 47 | // segment out of the window---should get an ACK 48 | test_1.send_byte(base_seq + cfg.recv_capacity, base_seq, 1); 49 | 50 | test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on late seqno"); 51 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on late seqno"); 52 | 53 | // segment in the window but late---should get an ACK and seg should be queued 54 | test_1.send_byte(base_seq + cfg.recv_capacity - 1, base_seq, 1); 55 | 56 | test_1.execute(ExpectUnassembledBytes{1}, "seg not queued on end-of-window seqno"); 57 | 58 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on end-of-window seqno"); 59 | test_1.execute(ExpectNoData{}, "test 1 failed: no ack on end-of-window seqno"); 60 | 61 | // segment next byte in the window - ack should advance and data should be readable 62 | test_1.send_byte(base_seq, base_seq, 1); 63 | 64 | test_1.execute(ExpectUnassembledBytes{1}, "seg not processed on next seqno"); 65 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq + 1), "test 1 failed: no ack on next seqno"); 66 | test_1.execute(ExpectData{}, "test 1 failed: no ack on next seqno"); 67 | 68 | test_1.send_rst(base_seq + 1); 69 | test_1.execute(ExpectState{State::RESET}); 70 | } 71 | } catch (const exception &e) { 72 | cerr << e.what() << endl; 73 | return EXIT_FAILURE; 74 | } 75 | 76 | return EXIT_SUCCESS; 77 | } 78 | -------------------------------------------------------------------------------- /tests/fsm_listen.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | using State = TCPTestHarness::State; 16 | 17 | int main() { 18 | try { 19 | TCPConfig cfg{}; 20 | 21 | // test #1: START -> LISTEN -> data without ACK or SYN (dropped) -> SYN -> SYN/ACK -> ACK 22 | { 23 | TCPTestHarness test_1(cfg); 24 | 25 | // tell the FSM to connect, make sure we get a SYN 26 | test_1.execute(Listen{}); 27 | test_1.execute(Tick(1)); 28 | 29 | test_1.send_byte(WrappingInt32{0}, {}, 0); 30 | test_1.send_fin(WrappingInt32{0}, {}); 31 | test_1.execute(Tick(1)); 32 | test_1.execute(ExpectState{State::LISTEN}); 33 | test_1.execute(ExpectNoSegment{}, "test 1 failed: non-ACK data segment should be ignored"); 34 | 35 | test_1.send_syn(WrappingInt32{0}, {}); 36 | test_1.execute(Tick(1)); 37 | 38 | TCPSegment seg = test_1.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(true).with_ackno(1), 39 | "test 1 failed: no SYN/ACK in response to SYN"); 40 | test_1.execute(ExpectState{State::SYN_RCVD}); 41 | 42 | // wrong seqno! should get ACK back but not transition 43 | const WrappingInt32 syn_seqno = seg.header().seqno; 44 | test_1.send_ack(WrappingInt32{0}, syn_seqno + 1); 45 | test_1.execute(Tick(1)); 46 | 47 | test_1.execute( 48 | ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(1).with_seqno(seg.header().seqno + 1), 49 | "test 1 failed: wrong response to old seqno"); 50 | 51 | test_1.send_ack(WrappingInt32(cfg.recv_capacity + 1), syn_seqno + 1); 52 | test_1.execute(Tick(1)); 53 | 54 | test_1.execute( 55 | ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(1).with_seqno(seg.header().seqno + 1)); 56 | 57 | test_1.send_ack(WrappingInt32{1}, seg.header().seqno + 1); 58 | test_1.execute(Tick(1)); 59 | test_1.execute(ExpectNoSegment{}, "test 1 failed: no need to ACK an ACK"); 60 | test_1.execute(ExpectState{State::ESTABLISHED}); 61 | } 62 | } catch (const exception &e) { 63 | cerr << e.what() << endl; 64 | return 1; 65 | } 66 | 67 | return EXIT_SUCCESS; 68 | } 69 | -------------------------------------------------------------------------------- /tests/fsm_listen_relaxed.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | using State = TCPTestHarness::State; 16 | 17 | int main() { 18 | try { 19 | TCPConfig cfg{}; 20 | 21 | // test #1: START -> LISTEN -> SYN -> SYN/ACK -> ACK 22 | { 23 | TCPTestHarness test_1(cfg); 24 | 25 | // tell the FSM to connect, make sure we get a SYN 26 | test_1.execute(Listen{}); 27 | test_1.execute(ExpectState{State::LISTEN}); 28 | test_1.execute(Tick(1)); 29 | test_1.execute(ExpectState{State::LISTEN}); 30 | 31 | test_1.send_syn(WrappingInt32{0}, {}); 32 | test_1.execute(Tick(1)); 33 | 34 | TCPSegment seg = test_1.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(true).with_ackno(1), 35 | "test 1 failed: no SYN/ACK in response to SYN"); 36 | test_1.execute(ExpectState{State::SYN_RCVD}); 37 | 38 | /* remove requirement for corrective ACK in response to out-of-window seqno 39 | const WrappingInt32 syn_seqno = seg.header().seqno; 40 | test_1.send_ack(WrappingInt32{0}, syn_seqno + 1); 41 | test_1.execute(Tick(1)); 42 | 43 | test_1.execute( 44 | ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(1).with_seqno(seg.header().seqno + 1), 45 | "test 1 failed: wrong response to old seqno"); 46 | 47 | test_1.send_ack(WrappingInt32(cfg.recv_capacity + 1), syn_seqno + 1); 48 | test_1.execute(Tick(1)); 49 | 50 | test_1.execute( 51 | ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(1).with_seqno(seg.header().seqno + 1)); 52 | */ 53 | 54 | test_1.send_ack(WrappingInt32{1}, seg.header().seqno + 1); 55 | test_1.execute(Tick(1)); 56 | test_1.execute(ExpectNoSegment{}, "test 1 failed: no need to ACK an ACK"); 57 | test_1.execute(ExpectState{State::ESTABLISHED}); 58 | } 59 | } catch (const exception &e) { 60 | cerr << e.what() << endl; 61 | return 1; 62 | } 63 | 64 | return EXIT_SUCCESS; 65 | } 66 | -------------------------------------------------------------------------------- /tests/fsm_loopback.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | #include "util.hh" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | using State = TCPTestHarness::State; 20 | 21 | static constexpr unsigned NREPS = 64; 22 | 23 | int main() { 24 | try { 25 | TCPConfig cfg{}; 26 | cfg.recv_capacity = 65000; 27 | auto rd = get_random_generator(); 28 | 29 | // loop segments back into the same FSM 30 | for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) { 31 | const WrappingInt32 rx_offset(rd()); 32 | TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, rx_offset - 1, rx_offset - 1); 33 | test_1.send_ack(rx_offset, rx_offset, 65000); 34 | 35 | string d(cfg.recv_capacity, 0); 36 | generate(d.begin(), d.end(), [&] { return rd(); }); 37 | 38 | size_t sendoff = 0; 39 | while (sendoff < d.size()) { 40 | const size_t len = min(d.size() - sendoff, static_cast(rd()) % 8192); 41 | if (len == 0) { 42 | continue; 43 | } 44 | test_1.execute(Write{d.substr(sendoff, len)}); 45 | test_1.execute(Tick(1)); 46 | test_1.execute(ExpectBytesInFlight{len}); 47 | 48 | test_1.execute(ExpectSegmentAvailable{}, "test 1 failed: cannot read after write()"); 49 | 50 | size_t n_segments = ceil(double(len) / TCPConfig::MAX_PAYLOAD_SIZE); 51 | size_t bytes_remaining = len; 52 | 53 | // Transfer the data segments 54 | for (size_t i = 0; i < n_segments; ++i) { 55 | size_t expected_size = min(bytes_remaining, TCPConfig::MAX_PAYLOAD_SIZE); 56 | auto seg = test_1.expect_seg(ExpectSegment{}.with_payload_size(expected_size)); 57 | bytes_remaining -= expected_size; 58 | test_1.execute(SendSegment{move(seg)}); 59 | test_1.execute(Tick(1)); 60 | } 61 | 62 | // Transfer the (bare) ack segments 63 | for (size_t i = 0; i < n_segments; ++i) { 64 | auto seg = test_1.expect_seg(ExpectSegment{}.with_ack(true).with_payload_size(0)); 65 | test_1.execute(SendSegment{move(seg)}); 66 | test_1.execute(Tick(1)); 67 | } 68 | test_1.execute(ExpectNoSegment{}); 69 | 70 | test_1.execute(ExpectBytesInFlight{0}); 71 | 72 | sendoff += len; 73 | } 74 | 75 | test_1.execute(ExpectData{}.with_data(d), "test 1 falied: got back the wrong data"); 76 | } 77 | } catch (const exception &e) { 78 | cerr << e.what() << endl; 79 | return EXIT_FAILURE; 80 | } 81 | 82 | return EXIT_SUCCESS; 83 | } 84 | -------------------------------------------------------------------------------- /tests/fsm_loopback_win.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | #include "util.hh" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | using State = TCPTestHarness::State; 20 | 21 | static constexpr unsigned NREPS = 32; 22 | 23 | int main() { 24 | try { 25 | TCPConfig cfg{}; 26 | cfg.recv_capacity = 65000; 27 | auto rd = get_random_generator(); 28 | 29 | // loop segments back in a different order 30 | for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) { 31 | const WrappingInt32 rx_offset(rd()); 32 | TCPTestHarness test_2 = TCPTestHarness::in_established(cfg, rx_offset - 1, rx_offset - 1); 33 | test_2.send_ack(rx_offset, rx_offset, 65000); 34 | 35 | string d(cfg.recv_capacity, 0); 36 | generate(d.begin(), d.end(), [&] { return rd(); }); 37 | 38 | vector segs; 39 | size_t sendoff = 0; 40 | while (sendoff < d.size()) { 41 | const size_t len = min(d.size() - sendoff, static_cast(rd()) % 8192); 42 | if (len == 0) { 43 | continue; 44 | } 45 | test_2.execute(Write{d.substr(sendoff, len)}); 46 | test_2.execute(Tick(1)); 47 | 48 | test_2.execute(ExpectSegmentAvailable{}, "test 2 failed: cannot read after write()"); 49 | while (test_2.can_read()) { 50 | segs.emplace_back(test_2.expect_seg(ExpectSegment{})); 51 | } 52 | sendoff += len; 53 | } 54 | 55 | // resend them in shuffled order 56 | vector seg_idx(segs.size(), 0); 57 | iota(seg_idx.begin(), seg_idx.end(), 0); 58 | shuffle(seg_idx.begin(), seg_idx.end(), rd); 59 | vector acks; 60 | for (auto idx : seg_idx) { 61 | test_2.execute(SendSegment{std::move(segs[idx])}); 62 | test_2.execute(Tick(1)); 63 | TCPSegment s = test_2.expect_seg(ExpectOneSegment{}.with_ack(true), "test 2 failed: no ACK after rcvd"); 64 | acks.emplace_back(std::move(s)); 65 | test_2.execute(ExpectNoSegment{}, "test 2 failed: double ACK?"); 66 | } 67 | 68 | // send just the final ack 69 | test_2.execute(SendSegment{std::move(acks.back())}); 70 | test_2.execute(ExpectNoSegment{}, "test 2 failed: ACK for ACK?"); 71 | 72 | test_2.execute(ExpectData{}.with_data(d), "test 2 failed: wrong data after loopback"); 73 | } 74 | } catch (const exception &e) { 75 | cerr << e.what() << endl; 76 | return EXIT_FAILURE; 77 | } 78 | 79 | return EXIT_SUCCESS; 80 | } 81 | -------------------------------------------------------------------------------- /tests/fsm_retx.cc: -------------------------------------------------------------------------------- 1 | #include "fsm_retx.hh" 2 | 3 | #include "tcp_config.hh" 4 | #include "tcp_expectation.hh" 5 | #include "util.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | using State = TCPTestHarness::State; 13 | 14 | int main() { 15 | try { 16 | TCPConfig cfg{}; 17 | cfg.recv_capacity = 65000; 18 | auto rd = get_random_generator(); 19 | 20 | // NOTE: the timeouts in this test are carefully adjusted to work whether the tcp_state_machine sends 21 | // immediately upon write() or only after the next tick(). If you edit this test, make sure that 22 | // it passes both ways (i.e., with and without calling _try_send() in TCPConnection::write()) 23 | 24 | // NOTE 2: ACK -- I think I was successful, although given unrelated 25 | // refactor to template code it will be more challenging now 26 | // to wait until tick() to send an outgoing segment. 27 | 28 | // single segment re-transmit 29 | { 30 | const WrappingInt32 tx_ackno(rd()); 31 | TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, tx_ackno - 1, tx_ackno - 1); 32 | 33 | string data = "asdf"; 34 | test_1.execute(Write{data}); 35 | test_1.execute(Tick(1)); 36 | 37 | check_segment(test_1, data, false, __LINE__); 38 | 39 | test_1.execute(Tick(cfg.rt_timeout - 2)); 40 | test_1.execute(ExpectNoSegment{}, "test 1 failed: re-tx too fast"); 41 | 42 | test_1.execute(Tick(2)); 43 | check_segment(test_1, data, false, __LINE__); 44 | 45 | test_1.execute(Tick(10 * cfg.rt_timeout + 100)); 46 | check_segment(test_1, data, false, __LINE__); 47 | 48 | for (unsigned i = 2; i < TCPConfig::MAX_RETX_ATTEMPTS; ++i) { 49 | test_1.execute(Tick((cfg.rt_timeout << i) - i)); // exponentially increasing delay length 50 | test_1.execute(ExpectNoSegment{}, "test 1 failed: re-tx too fast after timeout"); 51 | test_1.execute(Tick(i)); 52 | check_segment(test_1, data, false, __LINE__); 53 | } 54 | 55 | test_1.execute(ExpectState{State::ESTABLISHED}); 56 | 57 | test_1.execute(Tick(1 + (cfg.rt_timeout << TCPConfig::MAX_RETX_ATTEMPTS))); 58 | test_1.execute(ExpectState{State::RESET}); 59 | test_1.execute(ExpectOneSegment{}.with_rst(true).with_ack(false).with_seqno(tx_ackno), 60 | "test 1 failed: RST on re-tx failure was malformed"); 61 | } 62 | } catch (const exception &e) { 63 | cerr << e.what() << endl; 64 | return 1; 65 | } 66 | 67 | return EXIT_SUCCESS; 68 | } 69 | -------------------------------------------------------------------------------- /tests/fsm_retx.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_TESTS_FSM_RETX_HH 2 | #define SPONGE_TESTS_FSM_RETX_HH 3 | 4 | #include "tcp_expectation.hh" 5 | #include "tcp_fsm_test_harness.hh" 6 | #include "tcp_header.hh" 7 | #include "tcp_segment.hh" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | static void check_segment(TCPTestHarness &test, const std::string &data, const bool multiple, const int lineno) { 16 | try { 17 | std::cerr << " check_segment" << std::endl; 18 | test.execute(ExpectSegment{}.with_ack(true).with_payload_size(data.size()).with_data(data)); 19 | if (!multiple) { 20 | test.execute(ExpectNoSegment{}, "test failed: multiple re-tx?"); 21 | } 22 | } catch (const std::exception &e) { 23 | throw std::runtime_error(std::string(e.what()) + " (in check_segment called from line " + 24 | std::to_string(lineno) + ")"); 25 | } 26 | } 27 | 28 | #endif // SPONGE_TESTS_FSM_RETX_HH 29 | -------------------------------------------------------------------------------- /tests/fsm_retx_relaxed.cc: -------------------------------------------------------------------------------- 1 | #include "fsm_retx.hh" 2 | #include "tcp_config.hh" 3 | #include "tcp_expectation.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | using State = TCPTestHarness::State; 12 | 13 | int main() { 14 | try { 15 | TCPConfig cfg{}; 16 | cfg.recv_capacity = 65000; 17 | auto rd = get_random_generator(); 18 | 19 | // NOTE: the timeouts in this test are carefully adjusted to work whether the tcp_state_machine sends 20 | // immediately upon write() or only after the next tick(). If you edit this test, make sure that 21 | // it passes both ways (i.e., with and without calling _try_send() in TCPConnection::write()) 22 | 23 | // NOTE 2: ACK -- I think I was successful, although given unrelated 24 | // refactor to template code it will be more challenging now 25 | // to wait until tick() to send an outgoing segment. 26 | 27 | // single segment re-transmit 28 | { 29 | WrappingInt32 tx_ackno(rd()); 30 | TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, tx_ackno - 1, tx_ackno - 1); 31 | 32 | string data = "asdf"; 33 | test_1.execute(Write{data}); 34 | test_1.execute(Tick(1)); 35 | 36 | check_segment(test_1, data, false, __LINE__); 37 | 38 | test_1.execute(Tick(cfg.rt_timeout - 2)); 39 | test_1.execute(ExpectNoSegment{}, "test 1 failed: re-tx too fast"); 40 | 41 | test_1.execute(Tick(2)); 42 | check_segment(test_1, data, false, __LINE__); 43 | 44 | test_1.execute(Tick(10 * cfg.rt_timeout + 100)); 45 | check_segment(test_1, data, false, __LINE__); 46 | 47 | for (unsigned i = 2; i < TCPConfig::MAX_RETX_ATTEMPTS; ++i) { 48 | test_1.execute(Tick((cfg.rt_timeout << i) - i)); // exponentially increasing delay length 49 | test_1.execute(ExpectNoSegment{}, "test 1 failed: re-tx too fast after timeout"); 50 | test_1.execute(Tick(i)); 51 | check_segment(test_1, data, false, __LINE__); 52 | } 53 | 54 | test_1.execute(ExpectState{State::ESTABLISHED}); 55 | 56 | test_1.execute(Tick(1 + (cfg.rt_timeout << TCPConfig::MAX_RETX_ATTEMPTS))); 57 | test_1.execute(ExpectState{State::RESET}); 58 | test_1.execute(ExpectOneSegment{}.with_rst(true), "test 1 failed: RST on re-tx failure was malformed"); 59 | } 60 | } catch (const exception &e) { 61 | cerr << e.what() << endl; 62 | return 1; 63 | } 64 | 65 | return EXIT_SUCCESS; 66 | } 67 | -------------------------------------------------------------------------------- /tests/fsm_stream_reassembler_cap.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "fsm_stream_reassembler_harness.hh" 3 | #include "stream_reassembler.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | int main() { 12 | try { 13 | { 14 | ReassemblerTestHarness test{2}; 15 | 16 | test.execute(SubmitSegment{"ab", 0}); 17 | test.execute(BytesAssembled(2)); 18 | test.execute(BytesAvailable("ab")); 19 | 20 | test.execute(SubmitSegment{"cd", 2}); 21 | test.execute(BytesAssembled(4)); 22 | test.execute(BytesAvailable("cd")); 23 | 24 | test.execute(SubmitSegment{"ef", 4}); 25 | test.execute(BytesAssembled(6)); 26 | test.execute(BytesAvailable("ef")); 27 | } 28 | 29 | { 30 | ReassemblerTestHarness test{2}; 31 | 32 | test.execute(SubmitSegment{"ab", 0}); 33 | test.execute(BytesAssembled(2)); 34 | 35 | test.execute(SubmitSegment{"cd", 2}); 36 | test.execute(BytesAssembled(2)); 37 | 38 | test.execute(BytesAvailable("ab")); 39 | test.execute(BytesAssembled(2)); 40 | 41 | test.execute(SubmitSegment{"cd", 2}); 42 | test.execute(BytesAssembled(4)); 43 | 44 | test.execute(BytesAvailable("cd")); 45 | } 46 | 47 | { 48 | ReassemblerTestHarness test{2}; 49 | 50 | test.execute(SubmitSegment{"bX", 1}); 51 | test.execute(BytesAssembled(0)); 52 | 53 | test.execute(SubmitSegment{"a", 0}); 54 | test.execute(BytesAssembled(2)); 55 | 56 | test.execute(BytesAvailable("ab")); 57 | } 58 | 59 | { 60 | ReassemblerTestHarness test{1}; 61 | 62 | test.execute(SubmitSegment{"ab", 0}); 63 | test.execute(BytesAssembled(1)); 64 | 65 | test.execute(SubmitSegment{"ab", 0}); 66 | test.execute(BytesAssembled(1)); 67 | 68 | test.execute(BytesAvailable("a")); 69 | test.execute(BytesAssembled(1)); 70 | 71 | test.execute(SubmitSegment{"abc", 0}); 72 | test.execute(BytesAssembled(2)); 73 | 74 | test.execute(BytesAvailable("b")); 75 | test.execute(BytesAssembled(2)); 76 | } 77 | 78 | { 79 | ReassemblerTestHarness test{8}; 80 | 81 | test.execute(SubmitSegment{"a", 0}); 82 | test.execute(BytesAssembled(1)); 83 | test.execute(BytesAvailable("a")); 84 | test.execute(NotAtEof{}); 85 | 86 | test.execute(SubmitSegment{"bc", 1}); 87 | test.execute(BytesAssembled(3)); 88 | test.execute(NotAtEof{}); 89 | 90 | test.execute(SubmitSegment{"ghi", 6}.with_eof(true)); 91 | test.execute(BytesAssembled(3)); 92 | test.execute(NotAtEof{}); 93 | 94 | test.execute(SubmitSegment{"cdefg", 2}); 95 | test.execute(BytesAssembled(9)); 96 | test.execute(BytesAvailable{"bcdefghi"}); 97 | test.execute(AtEof{}); 98 | } 99 | 100 | { 101 | ReassemblerTestHarness test{3}; 102 | for (unsigned int i = 0; i < 99997; i += 3) { 103 | const string segment = {char(i), char(i + 1), char(i + 2), char(i + 13), char(i + 47), char(i + 9)}; 104 | test.execute(SubmitSegment{segment, i}); 105 | test.execute(BytesAssembled(i + 3)); 106 | test.execute(BytesAvailable(segment.substr(0, 3))); 107 | } 108 | } 109 | 110 | } catch (const exception &e) { 111 | cerr << "Exception: " << e.what() << endl; 112 | return EXIT_FAILURE; 113 | } 114 | 115 | return EXIT_SUCCESS; 116 | } 117 | -------------------------------------------------------------------------------- /tests/fsm_stream_reassembler_dup.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "fsm_stream_reassembler_harness.hh" 3 | #include "stream_reassembler.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | int main() { 12 | try { 13 | auto rd = get_random_generator(); 14 | 15 | { 16 | ReassemblerTestHarness test{65000}; 17 | 18 | test.execute(SubmitSegment{"abcd", 0}); 19 | test.execute(BytesAssembled(4)); 20 | test.execute(BytesAvailable("abcd")); 21 | test.execute(NotAtEof{}); 22 | 23 | test.execute(SubmitSegment{"abcd", 0}); 24 | test.execute(BytesAssembled(4)); 25 | test.execute(BytesAvailable("")); 26 | test.execute(NotAtEof{}); 27 | } 28 | 29 | { 30 | ReassemblerTestHarness test{65000}; 31 | 32 | test.execute(SubmitSegment{"abcd", 0}); 33 | test.execute(BytesAssembled(4)); 34 | test.execute(BytesAvailable("abcd")); 35 | test.execute(NotAtEof{}); 36 | 37 | test.execute(SubmitSegment{"abcd", 4}); 38 | test.execute(BytesAssembled(8)); 39 | test.execute(BytesAvailable("abcd")); 40 | test.execute(NotAtEof{}); 41 | 42 | test.execute(SubmitSegment{"abcd", 0}); 43 | test.execute(BytesAssembled(8)); 44 | test.execute(BytesAvailable("")); 45 | test.execute(NotAtEof{}); 46 | 47 | test.execute(SubmitSegment{"abcd", 4}); 48 | test.execute(BytesAssembled(8)); 49 | test.execute(BytesAvailable("")); 50 | test.execute(NotAtEof{}); 51 | } 52 | 53 | { 54 | ReassemblerTestHarness test{65000}; 55 | 56 | test.execute(SubmitSegment{"abcdefgh", 0}); 57 | test.execute(BytesAssembled(8)); 58 | test.execute(BytesAvailable("abcdefgh")); 59 | test.execute(NotAtEof{}); 60 | string data = "abcdefgh"; 61 | 62 | for (size_t i = 0; i < 1000; ++i) { 63 | size_t start_i = uniform_int_distribution{0, 8}(rd); 64 | auto start = data.begin(); 65 | std::advance(start, start_i); 66 | 67 | size_t end_i = uniform_int_distribution{start_i, 8}(rd); 68 | auto end = data.begin(); 69 | std::advance(end, end_i); 70 | 71 | test.execute(SubmitSegment{string{start, end}, start_i}); 72 | test.execute(BytesAssembled(8)); 73 | test.execute(BytesAvailable("")); 74 | test.execute(NotAtEof{}); 75 | } 76 | } 77 | 78 | { 79 | ReassemblerTestHarness test{65000}; 80 | 81 | test.execute(SubmitSegment{"abcd", 0}); 82 | test.execute(BytesAssembled(4)); 83 | test.execute(BytesAvailable("abcd")); 84 | test.execute(NotAtEof{}); 85 | 86 | test.execute(SubmitSegment{"abcdef", 0}); 87 | test.execute(BytesAssembled(6)); 88 | test.execute(BytesAvailable("ef")); 89 | test.execute(NotAtEof{}); 90 | } 91 | 92 | } catch (const exception &e) { 93 | cerr << "Exception: " << e.what() << endl; 94 | return EXIT_FAILURE; 95 | } 96 | 97 | return EXIT_SUCCESS; 98 | } 99 | -------------------------------------------------------------------------------- /tests/fsm_stream_reassembler_seq.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "fsm_stream_reassembler_harness.hh" 3 | #include "stream_reassembler.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | int main() { 12 | try { 13 | { 14 | ReassemblerTestHarness test{65000}; 15 | 16 | test.execute(SubmitSegment{"abcd", 0}); 17 | test.execute(BytesAssembled(4)); 18 | test.execute(BytesAvailable("abcd")); 19 | test.execute(NotAtEof{}); 20 | 21 | test.execute(SubmitSegment{"efgh", 4}); 22 | test.execute(BytesAssembled(8)); 23 | test.execute(BytesAvailable("efgh")); 24 | test.execute(NotAtEof{}); 25 | } 26 | 27 | { 28 | ReassemblerTestHarness test{65000}; 29 | 30 | test.execute(SubmitSegment{"abcd", 0}); 31 | test.execute(BytesAssembled(4)); 32 | test.execute(NotAtEof{}); 33 | test.execute(SubmitSegment{"efgh", 4}); 34 | test.execute(BytesAssembled(8)); 35 | 36 | test.execute(BytesAvailable("abcdefgh")); 37 | test.execute(NotAtEof{}); 38 | } 39 | 40 | { 41 | ReassemblerTestHarness test{65000}; 42 | std::ostringstream ss; 43 | 44 | for (size_t i = 0; i < 100; ++i) { 45 | test.execute(BytesAssembled(4 * i)); 46 | test.execute(SubmitSegment{"abcd", 4 * i}); 47 | test.execute(NotAtEof{}); 48 | 49 | ss << "abcd"; 50 | } 51 | 52 | test.execute(BytesAvailable(ss.str())); 53 | test.execute(NotAtEof{}); 54 | } 55 | 56 | { 57 | ReassemblerTestHarness test{65000}; 58 | std::ostringstream ss; 59 | 60 | for (size_t i = 0; i < 100; ++i) { 61 | test.execute(BytesAssembled(4 * i)); 62 | test.execute(SubmitSegment{"abcd", 4 * i}); 63 | test.execute(NotAtEof{}); 64 | 65 | test.execute(BytesAvailable("abcd")); 66 | } 67 | } 68 | 69 | } catch (const exception &e) { 70 | cerr << "Exception: " << e.what() << endl; 71 | return EXIT_FAILURE; 72 | } 73 | 74 | return EXIT_SUCCESS; 75 | } 76 | -------------------------------------------------------------------------------- /tests/fsm_stream_reassembler_win.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "stream_reassembler.hh" 3 | #include "util.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | 17 | static constexpr unsigned NREPS = 32; 18 | static constexpr unsigned NSEGS = 128; 19 | static constexpr unsigned MAX_SEG_LEN = 2048; 20 | 21 | string read(StreamReassembler &reassembler) { 22 | return reassembler.stream_out().read(reassembler.stream_out().buffer_size()); 23 | } 24 | 25 | int main() { 26 | try { 27 | auto rd = get_random_generator(); 28 | 29 | // overlapping segments 30 | for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) { 31 | StreamReassembler buf{NSEGS * MAX_SEG_LEN}; 32 | 33 | vector> seq_size; 34 | size_t offset = 0; 35 | for (unsigned i = 0; i < NSEGS; ++i) { 36 | const size_t size = 1 + (rd() % (MAX_SEG_LEN - 1)); 37 | const size_t offs = min(offset, 1 + (static_cast(rd()) % 1023)); 38 | seq_size.emplace_back(offset - offs, size + offs); 39 | offset += size; 40 | } 41 | shuffle(seq_size.begin(), seq_size.end(), rd); 42 | 43 | string d(offset, 0); 44 | generate(d.begin(), d.end(), [&] { return rd(); }); 45 | 46 | for (auto [off, sz] : seq_size) { 47 | string dd(d.cbegin() + off, d.cbegin() + off + sz); 48 | buf.push_substring(move(dd), off, off + sz == offset); 49 | } 50 | 51 | auto result = read(buf); 52 | if (buf.stream_out().bytes_written() != offset) { // read bytes 53 | throw runtime_error("test 2 - number of RX bytes is incorrect"); 54 | } 55 | if (!equal(result.cbegin(), result.cend(), d.cbegin())) { 56 | throw runtime_error("test 2 - content of RX bytes is incorrect"); 57 | } 58 | } 59 | } catch (const exception &e) { 60 | cerr << "Exception: " << e.what() << endl; 61 | return EXIT_FAILURE; 62 | } 63 | 64 | return EXIT_SUCCESS; 65 | } 66 | -------------------------------------------------------------------------------- /tests/ipv4_parser.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kiprey/sponge/33e6ba5ebe92f94fc0a177c2debacb3a04b6e300/tests/ipv4_parser.data -------------------------------------------------------------------------------- /tests/network_interface_test_harness.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_NETWORK_INTERFACE_HARNESS_HH 2 | #define SPONGE_NETWORK_INTERFACE_HARNESS_HH 3 | 4 | #include "network_interface.hh" 5 | 6 | #include 7 | 8 | struct NetworkInterfaceTestStep { 9 | virtual operator std::string() const; 10 | virtual void execute(NetworkInterface &) const; 11 | virtual ~NetworkInterfaceTestStep(); 12 | }; 13 | 14 | class NetworkInterfaceExpectationViolation : public std::runtime_error { 15 | public: 16 | NetworkInterfaceExpectationViolation(const std::string &msg); 17 | 18 | template 19 | static NetworkInterfaceExpectationViolation property(const std::string &property_name, 20 | const T &expected, 21 | const T &actual); 22 | }; 23 | 24 | struct NetworkInterfaceExpectation : public NetworkInterfaceTestStep { 25 | operator std::string() const override; 26 | virtual std::string description() const; 27 | virtual void execute(NetworkInterface &) const override; 28 | virtual ~NetworkInterfaceExpectation() override; 29 | }; 30 | 31 | struct NetworkInterfaceAction : public NetworkInterfaceTestStep { 32 | operator std::string() const override; 33 | virtual std::string description() const; 34 | virtual void execute(NetworkInterface &) const override; 35 | virtual ~NetworkInterfaceAction() override; 36 | }; 37 | 38 | struct SendDatagram : public NetworkInterfaceAction { 39 | InternetDatagram dgram; 40 | Address next_hop; 41 | 42 | std::string description() const override; 43 | void execute(NetworkInterface &interface) const override; 44 | 45 | SendDatagram(InternetDatagram d, Address n) : dgram(d), next_hop(n) {} 46 | }; 47 | 48 | struct ReceiveFrame : public NetworkInterfaceAction { 49 | EthernetFrame frame; 50 | std::optional expected; 51 | 52 | std::string description() const override; 53 | void execute(NetworkInterface &interface) const override; 54 | 55 | ReceiveFrame(EthernetFrame f, std::optional e) : frame(f), expected(e) {} 56 | }; 57 | 58 | struct ExpectFrame : public NetworkInterfaceExpectation { 59 | EthernetFrame expected; 60 | 61 | std::string description() const override; 62 | void execute(NetworkInterface &interface) const override; 63 | 64 | ExpectFrame(EthernetFrame e) : expected(e) {} 65 | }; 66 | 67 | struct ExpectNoFrame : public NetworkInterfaceExpectation { 68 | std::string description() const override; 69 | void execute(NetworkInterface &interface) const override; 70 | }; 71 | 72 | struct Tick : public NetworkInterfaceAction { 73 | size_t _ms; 74 | 75 | std::string description() const override; 76 | void execute(NetworkInterface &interface) const override; 77 | 78 | Tick(const size_t ms) : _ms(ms) {} 79 | }; 80 | 81 | class NetworkInterfaceTestHarness { 82 | std::string _test_name; 83 | NetworkInterface _interface; 84 | std::vector _steps_executed{}; 85 | 86 | public: 87 | NetworkInterfaceTestHarness(const std::string &test_name, 88 | const EthernetAddress ðernet_address, 89 | const Address &ip_address); 90 | 91 | void execute(const NetworkInterfaceTestStep &step); 92 | }; 93 | 94 | #endif // SPONGE_NETWORK_INTERFACE_HARNESS_HH 95 | -------------------------------------------------------------------------------- /tests/recv_close.cc: -------------------------------------------------------------------------------- 1 | #include "receiver_harness.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | int main() { 15 | try { 16 | auto rd = get_random_generator(); 17 | 18 | { 19 | uint32_t isn = uniform_int_distribution{0, UINT32_MAX}(rd); 20 | TCPReceiverTestHarness test{4000}; 21 | test.execute(ExpectState{TCPReceiverStateSummary::LISTEN}); 22 | test.execute(SegmentArrives{}.with_syn().with_seqno(isn + 0).with_result(SegmentArrives::Result::OK)); 23 | test.execute(ExpectState{TCPReceiverStateSummary::SYN_RECV}); 24 | test.execute(SegmentArrives{}.with_fin().with_seqno(isn + 1).with_result(SegmentArrives::Result::OK)); 25 | test.execute(ExpectAckno{WrappingInt32{isn + 2}}); 26 | test.execute(ExpectUnassembledBytes{0}); 27 | test.execute(ExpectBytes{""}); 28 | test.execute(ExpectTotalAssembledBytes{0}); 29 | test.execute(ExpectState{TCPReceiverStateSummary::FIN_RECV}); 30 | } 31 | 32 | { 33 | uint32_t isn = uniform_int_distribution{0, UINT32_MAX}(rd); 34 | TCPReceiverTestHarness test{4000}; 35 | test.execute(ExpectState{TCPReceiverStateSummary::LISTEN}); 36 | test.execute(SegmentArrives{}.with_syn().with_seqno(isn + 0).with_result(SegmentArrives::Result::OK)); 37 | test.execute(ExpectState{TCPReceiverStateSummary::SYN_RECV}); 38 | test.execute( 39 | SegmentArrives{}.with_fin().with_seqno(isn + 1).with_data("a").with_result(SegmentArrives::Result::OK)); 40 | test.execute(ExpectState{TCPReceiverStateSummary::FIN_RECV}); 41 | test.execute(ExpectAckno{WrappingInt32{isn + 3}}); 42 | test.execute(ExpectUnassembledBytes{0}); 43 | test.execute(ExpectBytes{"a"}); 44 | test.execute(ExpectTotalAssembledBytes{1}); 45 | test.execute(ExpectState{TCPReceiverStateSummary::FIN_RECV}); 46 | } 47 | 48 | } catch (const exception &e) { 49 | cerr << e.what() << endl; 50 | return 1; 51 | } 52 | 53 | return EXIT_SUCCESS; 54 | } 55 | -------------------------------------------------------------------------------- /tests/send_ack.cc: -------------------------------------------------------------------------------- 1 | #include "sender_harness.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | int main() { 15 | try { 16 | auto rd = get_random_generator(); 17 | 18 | { 19 | TCPConfig cfg; 20 | WrappingInt32 isn(rd()); 21 | cfg.fixed_isn = isn; 22 | 23 | TCPSenderTestHarness test{"Repeat ACK is ignored", cfg}; 24 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 25 | test.execute(ExpectNoSegment{}); 26 | test.execute(AckReceived{WrappingInt32{isn + 1}}); 27 | test.execute(WriteBytes{"a"}); 28 | test.execute(ExpectSegment{}.with_no_flags().with_data("a")); 29 | test.execute(ExpectNoSegment{}); 30 | test.execute(AckReceived{WrappingInt32{isn + 1}}); 31 | test.execute(ExpectNoSegment{}); 32 | } 33 | 34 | { 35 | TCPConfig cfg; 36 | WrappingInt32 isn(rd()); 37 | cfg.fixed_isn = isn; 38 | 39 | TCPSenderTestHarness test{"Old ACK is ignored", cfg}; 40 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 41 | test.execute(ExpectNoSegment{}); 42 | test.execute(AckReceived{WrappingInt32{isn + 1}}); 43 | test.execute(WriteBytes{"a"}); 44 | test.execute(ExpectSegment{}.with_no_flags().with_data("a")); 45 | test.execute(ExpectNoSegment{}); 46 | test.execute(AckReceived{WrappingInt32{isn + 2}}); 47 | test.execute(ExpectNoSegment{}); 48 | test.execute(WriteBytes{"b"}); 49 | test.execute(ExpectSegment{}.with_no_flags().with_data("b")); 50 | test.execute(ExpectNoSegment{}); 51 | test.execute(AckReceived{WrappingInt32{isn + 1}}); 52 | test.execute(ExpectNoSegment{}); 53 | } 54 | 55 | // credit for test: Jared Wasserman (2020) 56 | { 57 | TCPConfig cfg; 58 | WrappingInt32 isn(rd()); 59 | cfg.fixed_isn = isn; 60 | 61 | TCPSenderTestHarness test{"Impossible ackno (beyond next seqno) is ignored", cfg}; 62 | test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT}); 63 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 64 | test.execute(AckReceived{WrappingInt32{isn + 2}}.with_win(1000)); 65 | test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT}); 66 | } 67 | 68 | /* remove requirement to send corrective ACK for bad ACK 69 | { 70 | TCPConfig cfg; 71 | WrappingInt32 isn(rd()); 72 | cfg.fixed_isn = isn; 73 | 74 | TCPSenderTestHarness test{"Early ACK results in bare ACK", cfg}; 75 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 76 | test.execute(ExpectNoSegment{}); 77 | test.execute(AckReceived{WrappingInt32{isn + 1}}); 78 | test.execute(WriteBytes{"a"}); 79 | test.execute(ExpectSegment{}.with_no_flags().with_data("a")); 80 | test.execute(ExpectNoSegment{}); 81 | test.execute(AckReceived{WrappingInt32{isn + 17}}); 82 | test.execute(ExpectSegment{}.with_seqno(isn + 2)); 83 | test.execute(ExpectNoSegment{}); 84 | } 85 | */ 86 | 87 | } catch (const exception &e) { 88 | cerr << e.what() << endl; 89 | return 1; 90 | } 91 | 92 | return EXIT_SUCCESS; 93 | } 94 | -------------------------------------------------------------------------------- /tests/send_connect.cc: -------------------------------------------------------------------------------- 1 | #include "sender_harness.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | int main() { 15 | try { 16 | auto rd = get_random_generator(); 17 | 18 | { 19 | TCPConfig cfg; 20 | WrappingInt32 isn(rd()); 21 | cfg.fixed_isn = isn; 22 | 23 | TCPSenderTestHarness test{"SYN sent test", cfg}; 24 | test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT}); 25 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 26 | test.execute(ExpectBytesInFlight{1}); 27 | } 28 | 29 | { 30 | TCPConfig cfg; 31 | WrappingInt32 isn(rd()); 32 | cfg.fixed_isn = isn; 33 | 34 | TCPSenderTestHarness test{"SYN acked test", cfg}; 35 | test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT}); 36 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 37 | test.execute(ExpectBytesInFlight{1}); 38 | test.execute(AckReceived{WrappingInt32{isn + 1}}); 39 | test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED}); 40 | test.execute(ExpectNoSegment{}); 41 | test.execute(ExpectBytesInFlight{0}); 42 | } 43 | 44 | { 45 | TCPConfig cfg; 46 | WrappingInt32 isn(rd()); 47 | cfg.fixed_isn = isn; 48 | 49 | TCPSenderTestHarness test{"SYN -> wrong ack test", cfg}; 50 | test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT}); 51 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 52 | test.execute(ExpectBytesInFlight{1}); 53 | test.execute(AckReceived{WrappingInt32{isn}}); 54 | test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT}); 55 | test.execute(ExpectNoSegment{}); 56 | test.execute(ExpectBytesInFlight{1}); 57 | } 58 | 59 | { 60 | TCPConfig cfg; 61 | WrappingInt32 isn(rd()); 62 | cfg.fixed_isn = isn; 63 | 64 | TCPSenderTestHarness test{"SYN acked, data", cfg}; 65 | test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT}); 66 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 67 | test.execute(ExpectBytesInFlight{1}); 68 | test.execute(AckReceived{WrappingInt32{isn + 1}}); 69 | test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED}); 70 | test.execute(ExpectNoSegment{}); 71 | test.execute(ExpectBytesInFlight{0}); 72 | test.execute(WriteBytes{"abcdefgh"}); 73 | test.execute(Tick{1}); 74 | test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED}); 75 | test.execute(ExpectSegment{}.with_seqno(isn + 1).with_data("abcdefgh")); 76 | test.execute(ExpectBytesInFlight{8}); 77 | test.execute(AckReceived{WrappingInt32{isn + 9}}); 78 | test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED}); 79 | test.execute(ExpectNoSegment{}); 80 | test.execute(ExpectBytesInFlight{0}); 81 | test.execute(ExpectSeqno{WrappingInt32{isn + 9}}); 82 | } 83 | 84 | } catch (const exception &e) { 85 | cerr << e.what() << endl; 86 | return 1; 87 | } 88 | 89 | return EXIT_SUCCESS; 90 | } 91 | -------------------------------------------------------------------------------- /tests/send_equivalence_checker.cc: -------------------------------------------------------------------------------- 1 | #include "send_equivalence_checker.hh" 2 | 3 | #include "tcp_segment.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | void check_equivalent_segments(const TCPSegment &a, const TCPSegment &b) { 13 | if (not(a.header() == b.header())) { 14 | cerr << a.header().to_string() << endl; 15 | cerr << b.header().to_string() << endl; 16 | stringstream s{}; 17 | s << a.header().summary() << " with " << a.payload().size() << " bytes != " << b.header().summary() << " with " 18 | << b.payload().size() << " bytes"; 19 | throw runtime_error(s.str()); 20 | } 21 | if (a.payload().str() != b.payload().str()) { 22 | stringstream s{}; 23 | s << a.header().summary() << " with " << a.payload().size() << " bytes != " << b.header().summary() << " with " 24 | << b.payload().size() << " bytes"; 25 | throw runtime_error("unequal payloads: " + s.str()); 26 | } 27 | } 28 | 29 | void SendEquivalenceChecker::submit_a(TCPSegment &seg) { 30 | if (not bs.empty()) { 31 | check_equivalent_segments(seg, bs.front()); 32 | bs.pop_front(); 33 | } else { 34 | TCPSegment cp; 35 | cp.parse(seg.serialize()); 36 | as.emplace_back(move(cp)); 37 | } 38 | } 39 | 40 | void SendEquivalenceChecker::submit_b(TCPSegment &seg) { 41 | if (not as.empty()) { 42 | check_equivalent_segments(as.front(), seg); 43 | as.pop_front(); 44 | } else { 45 | TCPSegment cp; 46 | cp.parse(seg.serialize()); 47 | bs.emplace_back(move(cp)); 48 | } 49 | } 50 | 51 | void SendEquivalenceChecker::check_empty() const { 52 | if (not as.empty()) { 53 | stringstream s{}; 54 | s << as.size() << " unmatched packets from standard" << endl; 55 | for (const TCPSegment &a : as) { 56 | s << " - " << a.header().summary() << " with " << a.payload().size() << " bytes\n"; 57 | } 58 | throw runtime_error(s.str()); 59 | } 60 | if (not bs.empty()) { 61 | stringstream s{}; 62 | s << bs.size() << " unmatched packets from factored" << endl; 63 | for (const TCPSegment &a : bs) { 64 | s << " - " << a.header().summary() << " with " << a.payload().size() << " bytes\n"; 65 | } 66 | throw runtime_error(s.str()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/send_equivalence_checker.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_SEND_EQUIVALENCE_CHECKER_HH 2 | #define SPONGE_LIBSPONGE_SEND_EQUIVALENCE_CHECKER_HH 3 | 4 | #include "tcp_segment.hh" 5 | 6 | #include 7 | #include 8 | 9 | class SendEquivalenceChecker { 10 | std::deque as{}; 11 | std::deque bs{}; 12 | 13 | public: 14 | void submit_a(TCPSegment &seg); 15 | void submit_b(TCPSegment &seg); 16 | void check_empty() const; 17 | }; 18 | 19 | #endif // SPONGE_LIBSPONGE_SEND_EQUIVALENCE_CHECKER_HH 20 | -------------------------------------------------------------------------------- /tests/string_conversions.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_STRING_CONVERSIONS_HH 2 | #define SPONGE_STRING_CONVERSIONS_HH 3 | 4 | #include "wrapping_integers.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // https://stackoverflow.com/questions/33399594/making-a-user-defined-class-stdto-stringable 11 | 12 | namespace sponge_conversions { 13 | using std::to_string; 14 | 15 | std::string to_string(WrappingInt32 i) { return std::to_string(i.raw_value()); } 16 | 17 | template 18 | std::string to_string(const std::optional &v) { 19 | if (v.has_value()) { 20 | return "Some(" + to_string(v.value()) + ")"; 21 | } else { 22 | return "None"; 23 | } 24 | } 25 | 26 | template 27 | std::string as_string(T &&t) { 28 | return to_string(std::forward(t)); 29 | } 30 | } // namespace sponge_conversions 31 | 32 | template 33 | std::string to_string(T &&t) { 34 | return sponge_conversions::as_string(std::forward(t)); 35 | } 36 | 37 | #endif // SPONGE_STRING_CONVERSIONS_HH 38 | -------------------------------------------------------------------------------- /tests/tcp_expectation_forward.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TCP_EXPECTATION_FORWARD_HH 2 | #define SPONGE_LIBSPONGE_TCP_EXPECTATION_FORWARD_HH 3 | 4 | #include "tcp_segment.hh" 5 | #include "tcp_state.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class TCPTestHarness; 13 | 14 | struct TCPTestStep { 15 | public: 16 | virtual ~TCPTestStep() {} 17 | virtual std::string to_string() const { return "TestStep"; } 18 | virtual void execute(TCPTestHarness &) const {} 19 | virtual TCPSegment expect_seg(TCPTestHarness &) const { throw std::runtime_error("unimplemented"); } 20 | }; 21 | 22 | struct TCPExpectation; 23 | struct ExpectNoSegment; 24 | struct ExpectState; 25 | struct ExpectNotInState; 26 | struct ExpectOneSegment; 27 | struct ExpectSegment; 28 | struct ExpectData; 29 | struct ExpectNoData; 30 | struct ExpectSegmentAvailable; 31 | struct ExpectBytesInFlight; 32 | struct ExpectUnassembledBytes; 33 | struct ExpectWaitTimer; 34 | struct SendSegment; 35 | struct Write; 36 | struct Tick; 37 | struct Connect; 38 | struct Listen; 39 | struct Close; 40 | 41 | class TCPExpectationViolation : public std::runtime_error { 42 | public: 43 | TCPExpectationViolation(const std::string msg) : std::runtime_error(msg) {} 44 | }; 45 | 46 | class TCPPropertyViolation : public TCPExpectationViolation { 47 | public: 48 | TCPPropertyViolation(const std::string &msg) : TCPExpectationViolation(msg) {} 49 | template 50 | static TCPPropertyViolation make(const std::string &property_name, const T &expected_value, const T &actual_value) { 51 | std::stringstream ss{}; 52 | ss << "The TCP has `" << property_name << " = " << actual_value << "`, but " << property_name 53 | << " was expected to be `" << expected_value << "`"; 54 | return TCPPropertyViolation{ss.str()}; 55 | } 56 | 57 | template 58 | static TCPPropertyViolation make_not(const std::string &property_name, const T &expected_non_value) { 59 | std::stringstream ss{}; 60 | ss << "The TCP has `" << property_name << " = " << expected_non_value << "`, but " << property_name 61 | << " was expected to **not** be `" << expected_non_value << "`"; 62 | return TCPPropertyViolation{ss.str()}; 63 | } 64 | }; 65 | 66 | class SegmentExpectationViolation : public TCPExpectationViolation { 67 | public: 68 | SegmentExpectationViolation(const std::string &msg) : TCPExpectationViolation(msg) {} 69 | static SegmentExpectationViolation violated_verb(const std::string &msg) { 70 | return SegmentExpectationViolation{"The TCP should have produced a segment that " + msg + ", but it did not"}; 71 | } 72 | template 73 | static SegmentExpectationViolation violated_field(const std::string &field_name, 74 | const T &expected_value, 75 | const T &actual_value) { 76 | std::stringstream ss{}; 77 | ss << "The TCP produced a segment with `" << field_name << " = " << actual_value << "`, but " << field_name 78 | << " was expected to be `" << expected_value << "`"; 79 | return SegmentExpectationViolation{ss.str()}; 80 | } 81 | }; 82 | 83 | class StateExpectationViolation : public TCPExpectationViolation { 84 | public: 85 | StateExpectationViolation(const std::string &msg) : TCPExpectationViolation(msg) {} 86 | StateExpectationViolation(const TCPState &expected_state, const TCPState &actual_state) 87 | : TCPExpectationViolation("The TCP was in state `" + actual_state.name() + 88 | "`, but it was expected to be in state `" + expected_state.name() + "`") {} 89 | }; 90 | 91 | #endif // SPONGE_LIBSPONGE_TCP_EXPECTATION_FORWARD_HH 92 | -------------------------------------------------------------------------------- /tests/test_err_if.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_TESTS_TEST_ERR_IF_HH 2 | #define SPONGE_TESTS_TEST_ERR_IF_HH 3 | 4 | #include 5 | #include 6 | 7 | static int err_num = 1; 8 | 9 | #define test_err_if(c, s) _test_err_if(c, s, __LINE__) 10 | 11 | static void _test_err_if(const bool err_condition, const std::string &err_string, const int lineno) { 12 | if (err_condition) { 13 | throw std::runtime_error(err_string + " (at line " + std::to_string(lineno) + ")"); 14 | } 15 | ++err_num; 16 | } 17 | 18 | #endif // SPONGE_TESTS_TEST_ERR_IF_HH 19 | -------------------------------------------------------------------------------- /tests/test_should_be.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_TESTS_TEST_SHOULD_BE_HH 2 | #define SPONGE_TESTS_TEST_SHOULD_BE_HH 3 | 4 | #include "string_conversions.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define test_should_be(act, exp) _test_should_be(act, exp, #act, #exp, __LINE__) 12 | 13 | template 14 | static void _test_should_be(const T &actual, 15 | const T &expected, 16 | const char *actual_s, 17 | const char *expected_s, 18 | const int lineno) { 19 | if (actual != expected) { 20 | std::ostringstream ss; 21 | ss << "`" << actual_s << "` should have been `" << expected_s << "`, but the former is\n\t" << to_string(actual) 22 | << "\nand the latter is\n\t" << to_string(expected) << " (difference of " << expected - actual << ")\n" 23 | << " (at line " << lineno << ")\n"; 24 | throw std::runtime_error(ss.str()); 25 | } 26 | } 27 | 28 | #endif // SPONGE_TESTS_TEST_SHOULD_BE_HH 29 | -------------------------------------------------------------------------------- /tests/test_utils.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_TESTS_TEST_UTILS_HH 2 | #define SPONGE_TESTS_TEST_UTILS_HH 3 | 4 | #include "tcp_header.hh" 5 | #include "util.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | inline void show_ethernet_frame(const uint8_t *pkt, const struct pcap_pkthdr &hdr) { 13 | const auto flags(std::cout.flags()); 14 | 15 | std::cout << "source MAC: "; 16 | std::cout << std::hex << std::setfill('0'); 17 | for (unsigned i = 0; i < 6; ++i) { 18 | std::cout << std::setw(2) << +pkt[i] << (i == 5 ? ' ' : ':'); 19 | } 20 | 21 | std::cout << " dest MAC: "; 22 | for (unsigned i = 0; i < 6; ++i) { 23 | std::cout << std::setw(2) << +pkt[i + 6] << (i == 5 ? ' ' : ':'); 24 | } 25 | 26 | std::cout << " ethertype: " << std::setw(2) << +pkt[12] << std::setw(2) << +pkt[13] << '\n'; 27 | std::cout << std::dec << "length: " << hdr.len << " captured: " << hdr.caplen << '\n'; 28 | 29 | std::cout.flags(flags); 30 | } 31 | 32 | inline bool compare_tcp_headers_nolen(const TCPHeader &h1, const TCPHeader &h2) { 33 | return h1.sport == h2.sport && h1.dport == h2.dport && h1.seqno == h2.seqno && h1.ackno == h2.ackno && 34 | h1.urg == h2.urg && h1.ack == h2.ack && h1.psh == h2.psh && h1.rst == h2.rst && h1.syn == h2.syn && 35 | h1.fin == h2.fin && h1.win == h2.win && h1.uptr == h2.uptr; 36 | } 37 | 38 | inline bool compare_tcp_headers(const TCPHeader &h1, const TCPHeader &h2) { 39 | return compare_tcp_headers_nolen(h1, h2) && h1.doff == h2.doff; 40 | } 41 | 42 | #endif // SPONGE_TESTS_TEST_UTILS_HH 43 | -------------------------------------------------------------------------------- /tests/test_utils_ipv4.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_TESTS_TEST_UTILS_IPV4_HH 2 | #define SPONGE_TESTS_TEST_UTILS_IPV4_HH 3 | 4 | #include "ipv4_header.hh" 5 | 6 | inline bool compare_ip_headers_nolen(const IPv4Header &h1, const IPv4Header &h2) { 7 | return h1.proto == h2.proto && h1.src == h2.src && h1.dst == h2.dst && h1.ver == h2.ver && h1.tos == h2.tos && 8 | h1.id == h2.id && h1.df == h2.df && h1.mf == h2.mf && h1.offset == h2.offset && h1.ttl == h2.ttl; 9 | } 10 | 11 | inline bool compare_ip_headers(const IPv4Header &h1, const IPv4Header &h2) { 12 | return compare_ip_headers_nolen(h1, h2) && h1.hlen == h2.hlen && h1.len == h2.len && h1.cksum == h2.cksum; 13 | } 14 | 15 | #endif // SPONGE_TESTS_TEST_UTILS_IPV4_HH 16 | -------------------------------------------------------------------------------- /tests/webget_t.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WEB_HASH=`./apps/webget cs144.keithw.org /nph-hasher/xyzzy | tee /dev/stderr | tail -n 1` 4 | CORRECT_HASH="7SmXqWkrLKzVBCEalbSPqBcvs11Pw263K7x4Wv3JckI" 5 | 6 | if [ "${WEB_HASH}" != "${CORRECT_HASH}" ]; then 7 | echo ERROR: webget returned output that did not match the test\'s expectations 8 | exit 1 9 | fi 10 | exit 0 11 | -------------------------------------------------------------------------------- /tests/wrapping_integers_cmp.cc: -------------------------------------------------------------------------------- 1 | #include "test_should_be.hh" 2 | #include "util.hh" 3 | #include "wrapping_integers.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | int main() { 15 | try { 16 | // Comparing low-number adjacent seqnos 17 | test_should_be(WrappingInt32(3) != WrappingInt32(1), true); 18 | test_should_be(WrappingInt32(3) == WrappingInt32(1), false); 19 | 20 | size_t N_REPS = 4096; 21 | 22 | auto rd = get_random_generator(); 23 | 24 | for (size_t i = 0; i < N_REPS; i++) { 25 | uint32_t n = rd(); 26 | uint8_t diff = rd(); 27 | uint32_t m = n + diff; 28 | test_should_be(WrappingInt32(n) == WrappingInt32(m), n == m); 29 | test_should_be(WrappingInt32(n) != WrappingInt32(m), n != m); 30 | } 31 | 32 | } catch (const exception &e) { 33 | cerr << e.what() << endl; 34 | return 1; 35 | } 36 | 37 | return EXIT_SUCCESS; 38 | } 39 | -------------------------------------------------------------------------------- /tests/wrapping_integers_roundtrip.cc: -------------------------------------------------------------------------------- 1 | #include "util.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | void check_roundtrip(const WrappingInt32 isn, const uint64_t value, const uint64_t checkpoint) { 12 | if (unwrap(wrap(value, isn), isn, checkpoint) != value) { 13 | ostringstream ss; 14 | 15 | ss << "Expected unwrap(wrap()) to recover same value, and it didn't!\n"; 16 | ss << " unwrap(wrap(value, isn), isn, checkpoint) did not equal value\n"; 17 | ss << " where value = " << value << ", isn = " << isn << ", and checkpoint = " << checkpoint << "\n"; 18 | ss << " (Difference between value and checkpoint is " << value - checkpoint << ".)\n"; 19 | throw runtime_error(ss.str()); 20 | } 21 | } 22 | 23 | int main() { 24 | try { 25 | auto rd = get_random_generator(); 26 | uniform_int_distribution dist31minus1{0, (uint32_t{1} << 31) - 1}; 27 | uniform_int_distribution dist32{0, numeric_limits::max()}; 28 | uniform_int_distribution dist63{0, uint64_t{1} << 63}; 29 | 30 | const uint64_t big_offset = (uint64_t{1} << 31) - 1; 31 | 32 | for (unsigned int i = 0; i < 1000000; i++) { 33 | const WrappingInt32 isn{dist32(rd)}; 34 | const uint64_t val{dist63(rd)}; 35 | const uint64_t offset{dist31minus1(rd)}; 36 | 37 | check_roundtrip(isn, val, val); 38 | check_roundtrip(isn, val + 1, val); 39 | check_roundtrip(isn, val - 1, val); 40 | check_roundtrip(isn, val + offset, val); 41 | check_roundtrip(isn, val - offset, val); 42 | check_roundtrip(isn, val + big_offset, val); 43 | check_roundtrip(isn, val - big_offset, val); 44 | } 45 | } catch (const exception &e) { 46 | cerr << e.what() << endl; 47 | return 1; 48 | } 49 | 50 | return EXIT_SUCCESS; 51 | } 52 | -------------------------------------------------------------------------------- /tests/wrapping_integers_unwrap.cc: -------------------------------------------------------------------------------- 1 | #include "test_should_be.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | int main() { 13 | try { 14 | // Unwrap the first byte after ISN 15 | test_should_be(unwrap(WrappingInt32(1), WrappingInt32(0), 0), 1ul); 16 | // Unwrap the first byte after the first wrap 17 | test_should_be(unwrap(WrappingInt32(1), WrappingInt32(0), UINT32_MAX), (1ul << 32) + 1); 18 | // Unwrap the last byte before the third wrap 19 | test_should_be(unwrap(WrappingInt32(UINT32_MAX - 1), WrappingInt32(0), 3 * (1ul << 32)), 3 * (1ul << 32) - 2); 20 | // Unwrap the 10th from last byte before the third wrap 21 | test_should_be(unwrap(WrappingInt32(UINT32_MAX - 10), WrappingInt32(0), 3 * (1ul << 32)), 3 * (1ul << 32) - 11); 22 | // Non-zero ISN 23 | test_should_be(unwrap(WrappingInt32(UINT32_MAX), WrappingInt32(10), 3 * (1ul << 32)), 3 * (1ul << 32) - 11); 24 | // Big unwrap 25 | test_should_be(unwrap(WrappingInt32(UINT32_MAX), WrappingInt32(0), 0), static_cast(UINT32_MAX)); 26 | // Unwrap a non-zero ISN 27 | test_should_be(unwrap(WrappingInt32(16), WrappingInt32(16), 0), 0ul); 28 | 29 | // Big unwrap with non-zero ISN 30 | test_should_be(unwrap(WrappingInt32(15), WrappingInt32(16), 0), static_cast(UINT32_MAX)); 31 | // Big unwrap with non-zero ISN 32 | test_should_be(unwrap(WrappingInt32(0), WrappingInt32(INT32_MAX), 0), static_cast(INT32_MAX) + 2); 33 | // Barely big unwrap with non-zero ISN 34 | test_should_be(unwrap(WrappingInt32(UINT32_MAX), WrappingInt32(INT32_MAX), 0), static_cast(1) << 31); 35 | // Nearly big unwrap with non-zero ISN 36 | test_should_be(unwrap(WrappingInt32(UINT32_MAX), WrappingInt32(1ul << 31), 0), 37 | static_cast(UINT32_MAX) >> 1); 38 | } catch (const exception &e) { 39 | cerr << e.what() << endl; 40 | return 1; 41 | } 42 | 43 | return EXIT_SUCCESS; 44 | } 45 | -------------------------------------------------------------------------------- /tests/wrapping_integers_wrap.cc: -------------------------------------------------------------------------------- 1 | #include "test_should_be.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | 13 | int main() { 14 | try { 15 | test_should_be(wrap(3 * (1ll << 32), WrappingInt32(0)), WrappingInt32(0)); 16 | test_should_be(wrap(3 * (1ll << 32) + 17, WrappingInt32(15)), WrappingInt32(32)); 17 | test_should_be(wrap(7 * (1ll << 32) - 2, WrappingInt32(15)), WrappingInt32(13)); 18 | } catch (const exception &e) { 19 | cerr << e.what() << endl; 20 | return 1; 21 | } 22 | 23 | return EXIT_SUCCESS; 24 | } 25 | -------------------------------------------------------------------------------- /tun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | show_usage () { 4 | echo "Usage: $0 [tunnum ...]" 5 | exit 1 6 | } 7 | 8 | start_tun () { 9 | local TUNNUM="$1" TUNDEV="tun$1" 10 | ip tuntap add mode tun user "${SUDO_USER}" name "${TUNDEV}" 11 | ip addr add "${TUN_IP_PREFIX}.${TUNNUM}.1/24" dev "${TUNDEV}" 12 | ip link set dev "${TUNDEV}" up 13 | ip route change "${TUN_IP_PREFIX}.${TUNNUM}.0/24" dev "${TUNDEV}" rto_min 10ms 14 | 15 | # Apply NAT (masquerading) only to traffic from CS144's network devices 16 | iptables -t nat -A PREROUTING -s ${TUN_IP_PREFIX}.${TUNNUM}.0/24 -j CONNMARK --set-mark ${TUNNUM} 17 | iptables -t nat -A POSTROUTING -j MASQUERADE -m connmark --mark ${TUNNUM} 18 | echo 1 > /proc/sys/net/ipv4/ip_forward 19 | } 20 | 21 | stop_tun () { 22 | local TUNDEV="tun$1" 23 | iptables -t nat -D PREROUTING -s ${TUN_IP_PREFIX}.${1}.0/24 -j CONNMARK --set-mark ${1} 24 | iptables -t nat -D POSTROUTING -j MASQUERADE -m connmark --mark ${1} 25 | ip tuntap del mode tun name "$TUNDEV" 26 | } 27 | 28 | start_all () { 29 | while [ ! -z "$1" ]; do 30 | local INTF="$1"; shift 31 | start_tun "$INTF" 32 | done 33 | } 34 | 35 | stop_all () { 36 | while [ ! -z "$1" ]; do 37 | local INTF="$1"; shift 38 | stop_tun "$INTF" 39 | done 40 | } 41 | 42 | restart_all() { 43 | stop_all "$@" 44 | start_all "$@" 45 | } 46 | 47 | check_tun () { 48 | [ "$#" != 1 ] && { echo "bad params in check_tun"; exit 1; } 49 | local TUNDEV="tun${1}" 50 | # make sure tun is healthy: device is up, ip_forward is set, and iptables is configured 51 | ip link show ${TUNDEV} &>/dev/null || return 1 52 | [ "$(cat /proc/sys/net/ipv4/ip_forward)" = "1" ] || return 2 53 | } 54 | 55 | check_sudo () { 56 | if [ "$SUDO_USER" = "root" ]; then 57 | echo "please execute this script as a regular user, not as root" 58 | exit 1 59 | fi 60 | if [ -z "$SUDO_USER" ]; then 61 | # if the user didn't call us with sudo, re-execute 62 | exec sudo $0 "$MODE" "$@" 63 | fi 64 | } 65 | 66 | # check arguments 67 | if [ -z "$1" ] || ([ "$1" != "start" ] && [ "$1" != "stop" ] && [ "$1" != "restart" ] && [ "$1" != "check" ]); then 68 | show_usage 69 | fi 70 | MODE=$1; shift 71 | 72 | # set default argument 73 | if [ "$#" = "0" ]; then 74 | set -- 144 145 75 | fi 76 | 77 | # execute 'check' before trying to sudo 78 | # - like start, but exit successfully if everything is OK 79 | if [ "$MODE" = "check" ]; then 80 | declare -a INTFS 81 | MODE="start" 82 | while [ ! -z "$1" ]; do 83 | INTF="$1"; shift 84 | check_tun ${INTF} 85 | RET=$? 86 | if [ "$RET" = "0" ]; then 87 | continue 88 | fi 89 | 90 | if [ "$((RET > 1))" = "1" ]; then 91 | MODE="restart" 92 | fi 93 | INTFS+=($INTF) 94 | done 95 | 96 | # address only the interfaces that need it 97 | set -- "${INTFS[@]}" 98 | if [ "$#" = "0" ]; then 99 | exit 0 100 | fi 101 | echo -e "[$0] Bringing up tunnels ${INTFS[@]}:" 102 | fi 103 | 104 | # sudo if necessary 105 | check_sudo "$@" 106 | 107 | # get configuration 108 | . "$(dirname "$0")"/etc/tunconfig 109 | 110 | # start, stop, or restart all intfs 111 | eval "${MODE}_all" "$@" 112 | -------------------------------------------------------------------------------- /writeups/lab0.md: -------------------------------------------------------------------------------- 1 | Lab 0 Writeup 2 | ============= 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | I collaborated with: [list sunetids here] 9 | 10 | This lab took me about [n] hours to do. I [did/did not] attend the lab session. 11 | 12 | My secret code from section 2.1 was: [code here] 13 | 14 | - Optional: I had unexpected difficulty with: [describe] 15 | 16 | - Optional: I think you could make this lab better by: [describe] 17 | 18 | - Optional: I was surprised by: [describe] 19 | 20 | - Optional: I'm not sure about: [describe] 21 | -------------------------------------------------------------------------------- /writeups/lab1.md: -------------------------------------------------------------------------------- 1 | Lab 1 Writeup 2 | ============= 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | I collaborated with: [list sunetids here] 9 | 10 | I would like to thank/reward these classmates for their help: [list sunetids here] 11 | 12 | This lab took me about [n] hours to do. I [did/did not] attend the lab session. 13 | 14 | Program Structure and Design of the StreamReassembler: 15 | [] 16 | 17 | Implementation Challenges: 18 | [] 19 | 20 | Remaining Bugs: 21 | [] 22 | 23 | - Optional: I had unexpected difficulty with: [describe] 24 | 25 | - Optional: I think you could make this lab better by: [describe] 26 | 27 | - Optional: I was surprised by: [describe] 28 | 29 | - Optional: I'm not sure about: [describe] 30 | -------------------------------------------------------------------------------- /writeups/lab2.md: -------------------------------------------------------------------------------- 1 | Lab 2 Writeup 2 | ============= 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | I collaborated with: [list sunetids here] 9 | 10 | I would like to thank/reward these classmates for their help: [list sunetids here] 11 | 12 | This lab took me about [n] hours to do. I [did/did not] attend the lab session. 13 | 14 | Program Structure and Design of the TCPReceiver and wrap/unwrap routines: 15 | [] 16 | 17 | Implementation Challenges: 18 | [] 19 | 20 | Remaining Bugs: 21 | [] 22 | 23 | - Optional: I had unexpected difficulty with: [describe] 24 | 25 | - Optional: I think you could make this lab better by: [describe] 26 | 27 | - Optional: I was surprised by: [describe] 28 | 29 | - Optional: I'm not sure about: [describe] 30 | -------------------------------------------------------------------------------- /writeups/lab3.md: -------------------------------------------------------------------------------- 1 | Lab 3 Writeup 2 | ============= 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | I collaborated with: [list sunetids here] 9 | 10 | I would like to thank/reward these classmates for their help: [list sunetids here] 11 | 12 | This lab took me about [n] hours to do. I [did/did not] attend the lab session. 13 | 14 | Program Structure and Design of the TCPSender: 15 | [] 16 | 17 | Implementation Challenges: 18 | [] 19 | 20 | Remaining Bugs: 21 | [] 22 | 23 | - Optional: I had unexpected difficulty with: [describe] 24 | 25 | - Optional: I think you could make this lab better by: [describe] 26 | 27 | - Optional: I was surprised by: [describe] 28 | 29 | - Optional: I'm not sure about: [describe] 30 | -------------------------------------------------------------------------------- /writeups/lab4.md: -------------------------------------------------------------------------------- 1 | Lab 4 Writeup 2 | ============= 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | I collaborated with: [list sunetids here] 9 | 10 | I would like to thank/reward these classmates for their help: [list sunetids here] 11 | 12 | This lab took me about [n] hours to do. I [did/did not] attend the lab session. 13 | 14 | Program Structure and Design of the TCPConnection: 15 | [] 16 | 17 | Implementation Challenges: 18 | [] 19 | 20 | Remaining Bugs: 21 | [] 22 | 23 | - Optional: I had unexpected difficulty with: [describe] 24 | 25 | - Optional: I think you could make this lab better by: [describe] 26 | 27 | - Optional: I was surprised by: [describe] 28 | 29 | - Optional: I'm not sure about: [describe] 30 | -------------------------------------------------------------------------------- /writeups/lab5.md: -------------------------------------------------------------------------------- 1 | Lab 5 Writeup 2 | ============= 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | I collaborated with: [list sunetids here] 9 | 10 | I would like to thank/reward these classmates for their help: [list sunetids here] 11 | 12 | This lab took me about [n] hours to do. I [did/did not] attend the lab session. 13 | 14 | Program Structure and Design of the NetworkInterface: 15 | [] 16 | 17 | Implementation Challenges: 18 | [] 19 | 20 | Remaining Bugs: 21 | [] 22 | 23 | - Optional: I had unexpected difficulty with: [describe] 24 | 25 | - Optional: I think you could make this lab better by: [describe] 26 | 27 | - Optional: I was surprised by: [describe] 28 | 29 | - Optional: I'm not sure about: [describe] 30 | -------------------------------------------------------------------------------- /writeups/lab6.md: -------------------------------------------------------------------------------- 1 | Lab 6 Writeup 2 | ============= 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | I collaborated with: [list sunetids here] 9 | 10 | I would like to thank/reward these classmates for their help: [list sunetids here] 11 | 12 | This lab took me about [n] hours to do. I [did/did not] attend the lab session. 13 | 14 | Program Structure and Design of the Router: 15 | [] 16 | 17 | Implementation Challenges: 18 | [] 19 | 20 | Remaining Bugs: 21 | [] 22 | 23 | - Optional: I had unexpected difficulty with: [describe] 24 | 25 | - Optional: I think you could make this lab better by: [describe] 26 | 27 | - Optional: I was surprised by: [describe] 28 | 29 | - Optional: I'm not sure about: [describe] 30 | -------------------------------------------------------------------------------- /writeups/lab7.md: -------------------------------------------------------------------------------- 1 | Lab 7 Writeup 2 | ============= 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | My lab partner's SUNet ID: [your sunetid here] 9 | 10 | I also worked with or collaborated with: [their sunetids here] 11 | 12 | I would like to thank/reward these classmates for their help: [list sunetids here] 13 | 14 | This lab took me about [n] hours to do. I [did/did not] attend the lab session. 15 | 16 | Solo portion: 17 | [] 18 | 19 | Group portion: 20 | [] 21 | 22 | Creative portion (optional): 23 | [] 24 | 25 | Other remarks: 26 | [] 27 | 28 | - Optional: I had unexpected difficulty with: [describe] 29 | 30 | - Optional: I think you could make this lab better by: [describe] 31 | 32 | - Optional: I was surprised by: [describe] 33 | 34 | - Optional: I'm not sure about: [describe] 35 | --------------------------------------------------------------------------------