You’re welcome to use any Linux installation, in a VM or not, provided that it has a reasonably recent kernel (4.x should be fine) and a C++17 compiler.
Please note that the CAs will not be available to help you debug a BYO Linux installation. If you’re in doubt, please consider using the prepared image or following our VM setup instructions.
Packages you’ll need
To run the labs, you’ll need the following software:
g++ version 8.x
clang-tidy version 6 or 7
clang-format version 6 or 7
cmake version 3 or later
libpcap development headers (libpcap-dev on Debian-like distributions)
git
iptables
mininet 2.2.0 or later
tcpdump
telnet
wireshark
socat
netcat-openbsd
GNU coreutils
bash
doxygen
graphviz
Note: there are at least three versions of netcat. You won’t be using it much, but when you do, we will assume you’ve got the OpenBSD variant. Others might also work, though it is possible that they will require different commandline flags.
We’ve tested the labs with recent Debian-derived systems (Ubuntu 18.04 and similar) and Arch Linux. In all likelihood, other modern Linux distributions will also work.
资料主要参考课程文档
lab0
获取网页:
1 2 3 4 5
telnet cs144.keithw.org http //telnet 用于在本地计算机与远程计算机之间打开一个可靠的字节流,并在该计算机上运行特定的服务:http服务 GET /hello HTTP/1.1//告诉服务器URL的路径 Host: cs144.keithw.org //告诉服务器主机 Connection: close //告诉服务请完成请求,在完成回复后立刻关闭 //这里的话必须再按一次回车,用于告诉服务器你已经完成了http请求,否则将会收到 408 Request Timeout
#include<cstdlib> #include<iostream> #include<socket.hh> using namespace std;
voidget_URL(conststring &host, conststring &path) { // Your code here.
// You will need to connect to the "http" service on // the computer whose name is in the "host" string, // then request the URL path given in the "path" string.
// Then you'll need to print out everything the server sends back, // (not just one call to read() -- everything) until you reach // the "eof" (end of file).
intmain(int argc, char *argv[]) { try { if (argc <= 0) { abort(); // For sticklers: don't try to access argv[0] if argc <= 0. }
// The program takes two command-line arguments: the hostname and "path" part of the URL. // Print the usage message unless there are these two arguments (plus the program name // itself, so arg count = 3 in total). if (argc != 3) { cerr << "Usage: " << argv[0] << " HOST PATH\n"; cerr << "\tExample: " << argv[0] << " stanford.edu /class/cs144\n"; return EXIT_FAILURE; }
// Get the command-line arguments. conststring host = argv[1]; conststring path = argv[2];
#include<deque> #include<string> //! \brief An in-order byte stream.
//! Bytes are written on the "input" side and read from the "output" //! side. The byte stream is finite: the writer can end the input, //! and then no more bytes can be written. classByteStream { private: // Your code here -- add private members as necessary.
// Hint: This doesn't need to be a sophisticated data structure at // all, but if any of your tests are taking longer than a second, // that's a sign that you probably want to keep exploring // different approaches. std::deque<char> _stream_buffer; size_t _capacity ; size_t _read_cnt ; size_t _write_cnt ; bool _end_of_input ;
bool _error; //!< Flag indicating that the stream suffered an error.
public: //! Construct a stream with room for `capacity` bytes. ByteStream(constsize_t capacity);
//! \name "Input" interface for the writer //!@{
//! Write a string of bytes into the stream. Write as many //! as will fit, and return how many were written. //! \returns the number of bytes accepted into the stream size_twrite(const std::string &data);
//! \returns the number of additional bytes that the stream has space for size_tremaining_capacity()const;
//! Signal that the byte stream has reached its ending voidend_input();
//! Indicate that the stream suffered an error. voidset_error(){ _error = true; } //!@}
//! \name "Output" interface for the reader //!@{
//! Peek at next "len" bytes of the stream //! \returns a string std::string peek_output(constsize_t len)const;
//! Remove bytes from the buffer voidpop_output(constsize_t len);
//! Read (i.e., copy and then pop) the next "len" bytes of the stream //! \returns a string std::string read(constsize_t len);
//! \returns `true` if the stream input has ended boolinput_ended()const;
//! \returns `true` if the stream has suffered an error boolerror()const{ return _error; }
//! \returns the maximum amount that can currently be read from the stream size_tbuffer_size()const;
//! \returns `true` if the buffer is empty boolbuffer_empty()const;
//! \returns `true` if the output has reached the ending booleof()const; //!@}
//! \name General accounting //!@{
//! Total number of bytes written size_tbytes_written()const;
//! Total number of bytes popped size_tbytes_read()const; //!@} };
size_tByteStream::write(const string &data){ /* if (_end_of_input) return 0;*/
size_t len = min(data.length(), _capacity - _stream_buffer.size()); _write_cnt += len; for (size_t i = 0; i < len; i++) { _stream_buffer.push_back(data[i]); } return len; }
//! \param[in] len bytes will be copied from the output side of the buffer string ByteStream::peek_output(constsize_t len)const{ size_t peek_size = min(len, _stream_buffer.size()); returnstring(_stream_buffer.begin(), _stream_buffer.begin() + peek_size); }
//! \param[in] len bytes will be removed from the output side of the buffer voidByteStream::pop_output(constsize_t len){ size_t pop_size = min(len, _stream_buffer.size());
for (size_t i = 0; i < pop_size; i++) _stream_buffer.pop_front();
_read_cnt += pop_size; }
//! Read (i.e., copy and then pop) the next "len" bytes of the stream //! \param[in] len bytes will be popped and returned //! \returns a string std::string ByteStream::read(constsize_t len){ string data = this->peek_output(len); this->pop_output(len);
//! \brief A class that assembles a series of excerpts from a byte stream (possibly out of order, //! possibly overlapping) into an in-order byte stream. structbroken_bytestream { std::string _data; size_t _begin_index; size_t _len;
ssize_tmerge_fragments(broken_bytestream &t1, const broken_bytestream &t2); voidjudge_eof(constbool _eof); public: //! \brief Construct a `StreamReassembler` that will store up to `capacity` bytes. //! \note This capacity limits both the bytes that have been reassembled, //! and those that have not yet been reassembled. StreamReassembler(constsize_t capacity);
//! \brief Receive a substring and write any newly contiguous bytes into the stream. //! //! The StreamReassembler will stay within the memory limits of the `capacity`. //! Bytes that would exceed the capacity are silently discarded. //! //! \param data the substring //! \param index indicates the index (place in sequence) of the first byte in `data` //! \param eof the last byte of `data` will be the last byte in the entire stream voidpush_substring(const std::string &data, constuint64_t index, constbool eof);
//! The number of bytes in the substrings stored but not yet reassembled //! //! \note If the byte at a particular index has been pushed more than once, it //! should only be counted once for the purpose of this function. size_tunassembled_bytes()const;
//! \brief Is the internal state empty (other than the output stream)? //! \returns `true` if no substrings are waiting to be assembled boolempty()const; };
//! \details This function accepts a substring (aka a segment) of bytes, //! possibly out-of-order, from the logical stream, and assembles any newly //! contiguous substrings and writes them into the output stream in order.
voidStreamReassembler::judge_eof(constbool _eof){ if (_eof) { _eof_flag = true; } if (_eof_flag && empty()) { _output.end_input(); } }
//! 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) { return WrappingInt32{static_cast<uint32_t>(n) + isn.raw_value()}; }
//! 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.
// ( offset + a * 2^32)mod 2^32 = (offset)mod 2 ^ 32 = n // a = checkpoint >> 32
SYNs and FINs require acknowledgement, thus they increment the stream’s sequence number by one when used.For example, if the connection is closed without sending any more data, then if theFIN did not consume a sequencenumber the closing end couldn’t tell the difference between an ACK for the FIN,and an ACK for the data that was sent prior to the FIN.
... //! \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. classTCPReceiver { //! Our data structure for re-assembling bytes. StreamReassembler _reassembler; //! The maximum number of bytes we'll store. size_t _capacity; bool _syn_flag; WrappingInt32 _isn;
//! segments if the retransmission timer expires. classTCPSender { private: //! our initial sequence number, the number for our SYN. WrappingInt32 _isn; bool _set_syn_flag{false}; bool _set_fin_flag{false}; size_t _window_size{1};
size_t _timer{0};
size_t _bytes_in_flight{0}; //! outbound queue of segments that the TCPSender wants sent std::queue<TCPSegment> _segments_out{}; std::queue<TCPSegment> _segments_backup{}; //! retransmission timer for the connection size_t _retransmission_timeout{0}; size_t _consecutive_retransmissions_cnt{0}; unsignedint _initial_retransmission_timeout{};
//! \param[in] capacity the capacity of the outgoing byte stream //! \param[in] retx_timeout the initial amount of time to wait before retransmitting the oldest outstanding segment //! \param[in] fixed_isn the Initial Sequence Number to use, if set (otherwise uses a random ISN) TCPSender::TCPSender(constsize_t capacity, constuint16_t retx_timeout, const std::optional<WrappingInt32> fixed_isn) : _isn(fixed_isn.value_or(WrappingInt32{random_device()()})) , _initial_retransmission_timeout{retx_timeout} , _stream(capacity) {}
//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method voidTCPSender::tick(constsize_t ms_since_last_tick){ _timer+=ms_since_last_tick; if(!_segments_backup.empty() && (_timer >= _retransmission_timeout)){ if(_window_size) _retransmission_timeout*=2; _segments_out.push(_segments_backup.front()); _consecutive_retransmissions_cnt++; _timer=0; } }
voidTCPConnection::push_segments_out(){ while (!_sender.segments_out().empty()) { TCPSegment seg = _sender.segments_out().front(); _sender.segments_out().pop(); if (_receiver.ackno().has_value()) { seg.header().ack = true; seg.header().ackno = _receiver.ackno().value(); // raw_value seg.header().win = _receiver.window_size(); } _segments_out.push(seg); } }
//! \param[in] ms_since_last_tick number of milliseconds since the last call to this method voidTCPConnection::tick(constsize_t ms_since_last_tick){ // if(!_active) // return; _time_since_last_segment_received += ms_since_last_tick; _sender.tick(ms_since_last_tick); if (_sender.consecutive_retransmissions() > _cfg.MAX_RETX_ATTEMPTS) { // pop _sender.segments_out().pop(); unclean(true); return; } //_sender.fill_window(); push_segments_out();
classNetworkInterface { private: //! Ethernet (known as hardware, network-access-layer, or link-layer) address of the interface EthernetAddress _ethernet_address;
//! IP (known as internet-layer or network-layer) address of the interface Address _ip_address;
//! outbound queue of Ethernet frames that the NetworkInterface wants sent std::queue<EthernetFrame> _frames_out{};
// Dummy implementation of a network interface // Translates from {IP datagram, next hop address} to link-layer frame, and from link-layer frame to IP datagram
// For Lab 5, please replace with a real implementation that passes the // automated checks run by `make check_lab5`.
// You will need to add private members to the class declaration in `network_interface.hh`
//! \param[in] ethernet_address Ethernet (what ARP calls "hardware") address of the interface //! \param[in] ip_address IP (what ARP calls "protocol") address of the interface NetworkInterface::NetworkInterface(const EthernetAddress ðernet_address, const Address &ip_address) : _ethernet_address(ethernet_address), _ip_address(ip_address) { cerr << "DEBUG: Network interface has Ethernet address " << to_string(_ethernet_address) << " and IP address " << ip_address.ip() << "\n"; }
//! \param[in] dgram the IPv4 datagram to be sent //! \param[in] next_hop the IP address of the interface to send it to (typically a router or default gateway, but may also be another host if directly connected to the same network as the destination) //! (Note: the Address type can be converted to a uint32_t (raw 32-bit IP address) with the Address::ipv4_numeric() method.) voidNetworkInterface::send_datagram(const InternetDatagram &dgram, const Address &next_hop){ // convert IP address of next hop to raw 32-bit representation (used in ARP header) constuint32_t next_hop_ip = next_hop.ipv4_numeric(); // search in table auto iter = _arp_table.find(next_hop_ip);
if (iter == _arp_table.end()) { // not found in table if (_arp_searching_list.find(next_hop_ip) == _arp_searching_list.end()) { // havedn't send arp ARPMessage arp_msg; arp_msg.sender_ethernet_address = _ethernet_address; arp_msg.sender_ip_address = _ip_address.ipv4_numeric(); arp_msg.target_ethernet_address = {}; arp_msg.target_ip_address = next_hop_ip; arp_msg.opcode = ARPMessage::OPCODE_REQUEST;
//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method voidNetworkInterface::tick(constsize_t ms_since_last_tick){ for (auto iter = _arp_table.begin(); iter != _arp_table.end();) { if (iter->second.ttl <= ms_since_last_tick) { iter = _arp_table.erase(iter); } else { iter->second.ttl -= ms_since_last_tick; iter++; } }
for (auto iter = _arp_searching_list.begin(); iter != _arp_searching_list.end();) { if (iter->second <= ms_since_last_tick) { ARPMessage arp_msg; arp_msg.sender_ethernet_address = _ethernet_address; arp_msg.sender_ip_address = _ip_address.ipv4_numeric(); arp_msg.target_ethernet_address = {}; arp_msg.target_ip_address = iter->first; arp_msg.opcode = ARPMessage::OPCODE_REQUEST;