CS144 Lab checkpoint 2 starter code

This commit is contained in:
Keith Winstein 2021-09-28 16:50:45 -07:00
parent e36cd24d34
commit 87babd9d62
12 changed files with 531 additions and 0 deletions

View File

@ -0,0 +1,108 @@
#include "tcp_header.hh"
#include <sstream>
using namespace std;
//! \param[in,out] p is a NetParser from which the TCP fields will be extracted
//! \returns a ParseResult indicating success or the reason for failure
//! \details It is important to check for (at least) the following potential errors
//! (but note that NetParser inherently checks for certain errors;
//! use that fact to your advantage!):
//!
//! - data stream inside the NetParser is too short to contain a header
//! - the header's `doff` field is shorter than the minimum allowed
//! - there is less data in the header than the `doff` field claims
//! - the checksum is bad
ParseResult TCPHeader::parse(NetParser &p) {
sport = p.u16(); // source port
dport = p.u16(); // destination port
seqno = WrappingInt32{p.u32()}; // sequence number
ackno = WrappingInt32{p.u32()}; // ack number
doff = p.u8() >> 4; // data offset
const uint8_t fl_b = p.u8(); // byte including flags
urg = static_cast<bool>(fl_b & 0b0010'0000); // binary literals and ' digit separator since C++14!!!
ack = static_cast<bool>(fl_b & 0b0001'0000);
psh = static_cast<bool>(fl_b & 0b0000'1000);
rst = static_cast<bool>(fl_b & 0b0000'0100);
syn = static_cast<bool>(fl_b & 0b0000'0010);
fin = static_cast<bool>(fl_b & 0b0000'0001);
win = p.u16(); // window size
cksum = p.u16(); // checksum
uptr = p.u16(); // urgent pointer
if (doff < 5) {
return ParseResult::HeaderTooShort;
}
// skip any options or anything extra in the header
p.remove_prefix(doff * 4 - TCPHeader::LENGTH);
if (p.error()) {
return p.get_error();
}
return ParseResult::NoError;
}
//! Serialize the TCPHeader to a string (does not recompute the checksum)
string TCPHeader::serialize() const {
// sanity check
if (doff < 5) {
throw runtime_error("TCP header too short");
}
string ret;
ret.reserve(4 * doff);
NetUnparser::u16(ret, sport); // source port
NetUnparser::u16(ret, dport); // destination port
NetUnparser::u32(ret, seqno.raw_value()); // sequence number
NetUnparser::u32(ret, ackno.raw_value()); // ack number
NetUnparser::u8(ret, doff << 4); // data offset
const uint8_t fl_b = (urg ? 0b0010'0000 : 0) | (ack ? 0b0001'0000 : 0) | (psh ? 0b0000'1000 : 0) |
(rst ? 0b0000'0100 : 0) | (syn ? 0b0000'0010 : 0) | (fin ? 0b0000'0001 : 0);
NetUnparser::u8(ret, fl_b); // flags
NetUnparser::u16(ret, win); // window size
NetUnparser::u16(ret, cksum); // checksum
NetUnparser::u16(ret, uptr); // urgent pointer
ret.resize(4 * doff); // expand header to advertised size
return ret;
}
//! \returns A string with the header's contents
string TCPHeader::to_string() const {
stringstream ss{};
ss << hex << boolalpha << "TCP source port: " << +sport << '\n'
<< "TCP dest port: " << +dport << '\n'
<< "TCP seqno: " << seqno << '\n'
<< "TCP ackno: " << ackno << '\n'
<< "TCP doff: " << +doff << '\n'
<< "Flags: urg: " << urg << " ack: " << ack << " psh: " << psh << " rst: " << rst << " syn: " << syn
<< " fin: " << fin << '\n'
<< "TCP winsize: " << +win << '\n'
<< "TCP cksum: " << +cksum << '\n'
<< "TCP uptr: " << +uptr << '\n';
return ss.str();
}
string TCPHeader::summary() const {
stringstream ss{};
ss << "Header(flags=" << (syn ? "S" : "") << (ack ? "A" : "") << (rst ? "R" : "") << (fin ? "F" : "")
<< ",seqno=" << seqno << ",ack=" << ackno << ",win=" << win << ")";
return ss.str();
}
bool TCPHeader::operator==(const TCPHeader &other) const {
// TODO(aozdemir) more complete check (right now we omit cksum, src, dst
return seqno == other.seqno && ackno == other.ackno && doff == other.doff && urg == other.urg && ack == other.ack &&
psh == other.psh && rst == other.rst && syn == other.syn && fin == other.fin && win == other.win &&
uptr == other.uptr;
}

View File

@ -0,0 +1,68 @@
#ifndef SPONGE_LIBSPONGE_TCP_HEADER_HH
#define SPONGE_LIBSPONGE_TCP_HEADER_HH
#include "parser.hh"
#include "wrapping_integers.hh"
//! \brief [TCP](\ref rfc::rfc793) segment header
//! \note TCP options are not supported
struct TCPHeader {
static constexpr size_t LENGTH = 20; //!< [TCP](\ref rfc::rfc793) header length, not including options
//! \struct TCPHeader
//! ~~~{.txt}
//! 0 1 2 3
//! 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Source Port | Destination Port |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Sequence Number |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Acknowledgment Number |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Data | |U|A|P|R|S|F| |
//! | Offset| Reserved |R|C|S|S|Y|I| Window |
//! | | |G|K|H|T|N|N| |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Checksum | Urgent Pointer |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Options | Padding |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | data |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! ~~~
//! \name TCP Header fields
//!@{
uint16_t sport = 0; //!< source port
uint16_t dport = 0; //!< destination port
WrappingInt32 seqno{0}; //!< sequence number
WrappingInt32 ackno{0}; //!< ack number
uint8_t doff = LENGTH / 4; //!< data offset
bool urg = false; //!< urgent flag
bool ack = false; //!< ack flag
bool psh = false; //!< push flag
bool rst = false; //!< rst flag
bool syn = false; //!< syn flag
bool fin = false; //!< fin flag
uint16_t win = 0; //!< window size
uint16_t cksum = 0; //!< checksum
uint16_t uptr = 0; //!< urgent pointer
//!@}
//! Parse the TCP fields from the provided NetParser
ParseResult parse(NetParser &p);
//! Serialize the TCP fields
std::string serialize() const;
//! Return a string containing a header in human-readable format
std::string to_string() const;
//! Return a string containing a human-readable summary of the header
std::string summary() const;
bool operator==(const TCPHeader &other) const;
};
#endif // SPONGE_LIBSPONGE_TCP_HEADER_HH

View File

@ -0,0 +1,45 @@
#include "tcp_segment.hh"
#include "parser.hh"
#include "util.hh"
#include <variant>
using namespace std;
//! \param[in] buffer string/Buffer to be parsed
//! \param[in] datagram_layer_checksum pseudo-checksum from the lower-layer protocol
ParseResult TCPSegment::parse(const Buffer buffer, const uint32_t datagram_layer_checksum) {
InternetChecksum check(datagram_layer_checksum);
check.add(buffer);
if (check.value()) {
return ParseResult::BadChecksum;
}
NetParser p{buffer};
_header.parse(p);
_payload = p.buffer();
return p.get_error();
}
size_t TCPSegment::length_in_sequence_space() const {
return payload().str().size() + (header().syn ? 1 : 0) + (header().fin ? 1 : 0);
}
//! \param[in] datagram_layer_checksum pseudo-checksum from the lower-layer protocol
BufferList TCPSegment::serialize(const uint32_t datagram_layer_checksum) const {
TCPHeader header_out = _header;
header_out.cksum = 0;
// calculate checksum -- taken over entire segment
InternetChecksum check(datagram_layer_checksum);
check.add(header_out.serialize());
check.add(_payload);
header_out.cksum = check.value();
BufferList ret;
ret.append(header_out.serialize());
ret.append(_payload);
return ret;
}

View File

@ -0,0 +1,36 @@
#ifndef SPONGE_LIBSPONGE_TCP_SEGMENT_HH
#define SPONGE_LIBSPONGE_TCP_SEGMENT_HH
#include "buffer.hh"
#include "tcp_header.hh"
#include <cstdint>
//! \brief [TCP](\ref rfc::rfc793) segment
class TCPSegment {
private:
TCPHeader _header{};
Buffer _payload{};
public:
//! \brief Parse the segment from a string
ParseResult parse(const Buffer buffer, const uint32_t datagram_layer_checksum = 0);
//! \brief Serialize the segment to a string
BufferList serialize(const uint32_t datagram_layer_checksum = 0) const;
//! \name Accessors
//!@{
const TCPHeader &header() const { return _header; }
TCPHeader &header() { return _header; }
const Buffer &payload() const { return _payload; }
Buffer &payload() { return _payload; }
//!@}
//! \brief Segment's length in sequence space
//! \note Equal to payload length plus one byte if SYN is set, plus one byte if FIN is set
size_t length_in_sequence_space() const;
};
#endif // SPONGE_LIBSPONGE_TCP_SEGMENT_HH

View File

@ -0,0 +1,15 @@
#include "tcp_state.hh"
using namespace std;
string TCPState::state_summary(const TCPReceiver &receiver) {
if (receiver.stream_out().error()) {
return TCPReceiverStateSummary::ERROR;
} else if (not receiver.ackno().has_value()) {
return TCPReceiverStateSummary::LISTEN;
} else if (receiver.stream_out().input_ended()) {
return TCPReceiverStateSummary::FIN_RECV;
} else {
return TCPReceiverStateSummary::SYN_RECV;
}
}

View File

@ -0,0 +1,35 @@
#ifndef SPONGE_LIBSPONGE_TCP_STATE
#define SPONGE_LIBSPONGE_TCP_STATE
#include "tcp_receiver.hh"
#include <string>
//! \brief Summary of a TCPConnection's internal state
//!
//! Most TCP implementations have a global per-connection state
//! machine, as described in the [TCP](\ref rfc::rfc793)
//! specification. Sponge is a bit different: we have factored the
//! connection into two independent parts (the sender and the
//! receiver). The TCPSender and TCPReceiver maintain their interval
//! state variables independently (e.g. next_seqno, number of bytes in
//! flight, or whether each stream has ended). There is no notion of a
//! discrete state machine or much overarching state outside the
//! sender and receiver. To test that Sponge follows the TCP spec, we
//! use this class to compare the "official" states with Sponge's
//! sender/receiver states and two variables that belong to the
//! overarching TCPConnection object.
class TCPState {
public:
//! \brief Summarize the state of a TCPReceiver in a string
static std::string state_summary(const TCPReceiver &receiver);
};
namespace TCPReceiverStateSummary {
const std::string ERROR = "error (connection was reset)";
const std::string LISTEN = "waiting for SYN: ackno is empty";
const std::string SYN_RECV = "SYN received (ackno exists), and input to stream hasn't ended";
const std::string FIN_RECV = "input to stream has ended";
} // namespace TCPReceiverStateSummary
#endif // SPONGE_LIBSPONGE_TCP_STATE

19
libsponge/tcp_receiver.cc Normal file
View File

@ -0,0 +1,19 @@
#include "tcp_receiver.hh"
// Dummy implementation of a TCP receiver
// For Lab 2, please replace with a real implementation that passes the
// automated checks run by `make check_lab2`.
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
void TCPReceiver::segment_received(const TCPSegment &seg) {
DUMMY_CODE(seg);
}
optional<WrappingInt32> TCPReceiver::ackno() const { return {}; }
size_t TCPReceiver::window_size() const { return {}; }

66
libsponge/tcp_receiver.hh Normal file
View File

@ -0,0 +1,66 @@
#ifndef SPONGE_LIBSPONGE_TCP_RECEIVER_HH
#define SPONGE_LIBSPONGE_TCP_RECEIVER_HH
#include "byte_stream.hh"
#include "stream_reassembler.hh"
#include "tcp_segment.hh"
#include "wrapping_integers.hh"
#include <optional>
//! \brief The "receiver" part of a TCP implementation.
//! Receives and reassembles segments into a ByteStream, and computes
//! the acknowledgment number and window size to advertise back to the
//! remote TCPSender.
class TCPReceiver {
//! Our data structure for re-assembling bytes.
StreamReassembler _reassembler;
//! The maximum number of bytes we'll store.
size_t _capacity;
public:
//! \brief Construct a TCP receiver
//!
//! \param capacity the maximum number of bytes that the receiver will
//! store in its buffers at any give time.
TCPReceiver(const size_t capacity) : _reassembler(capacity), _capacity(capacity) {}
//! \name Accessors to provide feedback to the remote TCPSender
//!@{
//! \brief The ackno that should be sent to the peer
//! \returns empty if no SYN has been received
//!
//! This is the beginning of the receiver's window, or in other words, the sequence number
//! of the first byte in the stream that the receiver hasn't received.
std::optional<WrappingInt32> ackno() const;
//! \brief The window size that should be sent to the peer
//!
//! Operationally: the capacity minus the number of bytes that the
//! TCPReceiver is holding in its byte stream (those that have been
//! reassembled, but not consumed).
//!
//! Formally: the difference between (a) the sequence number of
//! the first byte that falls after the window (and will not be
//! accepted by the receiver) and (b) the sequence number of the
//! beginning of the window (the ackno).
size_t window_size() const;
//!@}
//! \brief number of bytes stored but not yet reassembled
size_t unassembled_bytes() const { return _reassembler.unassembled_bytes(); }
//! \brief handle an inbound segment
void segment_received(const TCPSegment &seg);
//! \name "Output" interface for the reader
//!@{
ByteStream &stream_out() { return _reassembler.stream_out(); }
const ByteStream &stream_out() const { return _reassembler.stream_out(); }
//!@}
};
#endif // SPONGE_LIBSPONGE_TCP_RECEIVER_HH

View File

@ -0,0 +1,34 @@
#include "wrapping_integers.hh"
// Dummy implementation of a 32-bit wrapping integer
// For Lab 2, please replace with a real implementation that passes the
// automated checks run by `make check_lab2`.
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
//! Transform an "absolute" 64-bit sequence number (zero-indexed) into a WrappingInt32
//! \param n The input absolute 64-bit sequence number
//! \param isn The initial sequence number
WrappingInt32 wrap(uint64_t n, WrappingInt32 isn) {
DUMMY_CODE(n, isn);
return WrappingInt32{0};
}
//! Transform a WrappingInt32 into an "absolute" 64-bit sequence number (zero-indexed)
//! \param n The relative sequence number
//! \param isn The initial sequence number
//! \param checkpoint A recent absolute 64-bit sequence number
//! \returns the 64-bit sequence number that wraps to `n` and is closest to `checkpoint`
//!
//! \note Each of the two streams of the TCP connection has its own ISN. One stream
//! runs from the local TCPSender to the remote TCPReceiver and has one ISN,
//! and the other stream runs from the remote TCPSender to the local TCPReceiver and
//! has a different ISN.
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) {
DUMMY_CODE(n, isn, checkpoint);
return {};
}

View File

@ -0,0 +1,65 @@
#ifndef SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH
#define SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH
#include <cstdint>
#include <ostream>
//! \brief A 32-bit integer, expressed relative to an arbitrary initial sequence number (ISN)
//! \note This is used to express TCP sequence numbers (seqno) and acknowledgment numbers (ackno)
class WrappingInt32 {
private:
uint32_t _raw_value; //!< The raw 32-bit stored integer
public:
//! Construct from a raw 32-bit unsigned integer
explicit WrappingInt32(uint32_t raw_value) : _raw_value(raw_value) {}
uint32_t raw_value() const { return _raw_value; } //!< Access raw stored value
};
//! Transform a 64-bit absolute sequence number (zero-indexed) into a 32-bit relative sequence number
//! \param n the absolute sequence number
//! \param isn the initial sequence number
//! \returns the relative sequence number
WrappingInt32 wrap(uint64_t n, WrappingInt32 isn);
//! Transform a 32-bit relative sequence number into a 64-bit absolute sequence number (zero-indexed)
//! \param n The relative sequence number
//! \param isn The initial sequence number
//! \param checkpoint A recent absolute sequence number
//! \returns the absolute sequence number that wraps to `n` and is closest to `checkpoint`
//!
//! \note Each of the two streams of the TCP connection has its own ISN. One stream
//! runs from the local TCPSender to the remote TCPReceiver and has one ISN,
//! and the other stream runs from the remote TCPSender to the local TCPReceiver and
//! has a different ISN.
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint);
//! \name Helper functions
//!@{
//! \brief The offset of `a` relative to `b`
//! \param b the starting point
//! \param a the ending point
//! \returns the number of increments needed to get from `b` to `a`,
//! negative if the number of decrements needed is less than or equal to
//! the number of increments
inline int32_t operator-(WrappingInt32 a, WrappingInt32 b) { return a.raw_value() - b.raw_value(); }
//! \brief Whether the two integers are equal.
inline bool operator==(WrappingInt32 a, WrappingInt32 b) { return a.raw_value() == b.raw_value(); }
//! \brief Whether the two integers are not equal.
inline bool operator!=(WrappingInt32 a, WrappingInt32 b) { return !(a == b); }
//! \brief Serializes the wrapping integer, `a`.
inline std::ostream &operator<<(std::ostream &os, WrappingInt32 a) { return os << a.raw_value(); }
//! \brief The point `b` steps past `a`.
inline WrappingInt32 operator+(WrappingInt32 a, uint32_t b) { return WrappingInt32{a.raw_value() + b}; }
//! \brief The point `b` steps before `a`.
inline WrappingInt32 operator-(WrappingInt32 a, uint32_t b) { return a + -b; }
//!@}
#endif // SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH

View File

@ -6,6 +6,7 @@ macro (add_test_exec exec_name)
target_link_libraries ("${exec_name}" sponge ${ARGN})
endmacro (add_test_exec)
add_test_exec (tcp_parser ${LIBPCAP})
add_test_exec (fsm_stream_reassembler_single)
add_test_exec (fsm_stream_reassembler_seq)
add_test_exec (fsm_stream_reassembler_dup)
@ -19,3 +20,13 @@ add_test_exec (byte_stream_one_write)
add_test_exec (byte_stream_two_writes)
add_test_exec (byte_stream_capacity)
add_test_exec (byte_stream_many_writes)
add_test_exec (wrapping_integers_cmp)
add_test_exec (wrapping_integers_unwrap)
add_test_exec (wrapping_integers_wrap)
add_test_exec (wrapping_integers_roundtrip)
add_test_exec (recv_connect)
add_test_exec (recv_transmit)
add_test_exec (recv_window)
add_test_exec (recv_reorder)
add_test_exec (recv_close)
add_test_exec (recv_special)

29
writeups/lab2.md Normal file
View File

@ -0,0 +1,29 @@
Lab 2 Writeup
=============
My name: [your name here]
My SUNet ID: [your sunetid here]
I collaborated with: [list sunetids here]
I would like to thank/reward these classmates for their help: [list sunetids here]
This lab took me about [n] hours to do. I [did/did not] attend the lab session.
Program Structure and Design of the TCPReceiver and wrap/unwrap routines:
[]
Implementation Challenges:
[]
Remaining Bugs:
[]
- Optional: I had unexpected difficulty with: [describe]
- Optional: I think you could make this lab better by: [describe]
- Optional: I was surprised by: [describe]
- Optional: I'm not sure about: [describe]