//! \param[in] len bytes will be copied from the output side of the buffer string ByteStream::peek_output(constsize_t len)const{ // DUMMY_CODE(len); string output = ""; size_t output_size = min(buffer_size(), len); for(size_t i = 0; i < output_size; ++i){ output += _deque[i]; } // _bytes_read += output_size; // READ-ONLY! return output; }
//! \param[in] len bytes will be removed from the output side of the buffer voidByteStream::pop_output(constsize_t len){ // DUMMY_CODE(len); size_t pop_size = min(buffer_size(), len); for(size_t i = 0; i < pop_size; ++i){ _deque.pop_front(); } _bytes_read += 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){ // DUMMY_CODE(len); string ret = peek_output(len); pop_output(len); return ret; }
Lab
Checkpoint 1: stitching substrings into a byte stream
In Lab 1, you’ll implement a stream
reassembler—a module that stitches small pieces of the
byte stream (known as substrings, or segments) back
into a contiguous stream of bytes in the correct sequence.
//! \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::push_substring(const string &data, constsize_t index, constbool eof){ // DUMMY_CODE(data, index, eof);
// The number of overlap chars. size_t overlap_size = 0;
// if out of bounds, discard it. if(index + data.size() < _first_unread || index > _first_unacceptable){ return; }
// if in unaassembled area, store it. if(index > _first_unread){ string substring = data.substr(0, min(_first_unacceptable - index, data.size())); // if never stored before if(_auxiliary_storage.find(index) == _auxiliary_storage.end()){ _auxiliary_storage[index] = substring; overlap_size = overlap_length(); if(_auxiliary_storage.find(index) == _auxiliary_storage.end()){ // if deleted after merge _unassembled_bytes += _auxiliary_storage[index].size() - _outside_size; }else{ _unassembled_bytes += _auxiliary_storage[index].size() - overlap_size; // c bcd ->error } }elseif(substring.size() > _auxiliary_storage[index].size()){ // update better(longer) substring size_t before_len = _auxiliary_storage[index].size(); _auxiliary_storage[index] = substring; overlap_size = overlap_length(); _unassembled_bytes += before_len + overlap_size; } }
In Lab 2, you will implement the
TCPReceiver, the part of a TCP implementation
that handles the incoming byte stream. The TCPReceiver
translates between incoming TCP segments (the payloads
of datagrams carried over the Internet) and the incoming byte
stream.
// seg with FIN had come before and all data has been wrtten if(_fin_flag && _reassembler.stream_out().input_ended()){ _ack_no = WrappingInt32(_ack_no.value() + 1); } }
Total Test time (real) = 0.51 sec [100%] Built target check_lab2
更新:将第 27 行代码更新为第 28 行代码 修复了下述情况遇到的错误
1 2
with_syn().with_seqno(5).with_fin() SYN received (ackno exists), and input to stream hasn't ended
seqno
2303251161
2303251162
2303251163
2303251164
2303251165
2303251166
2303251167
2303251168
2303251169
e
g
c
a
b
f
添加_first_unassembled的接口
修复了上述 bug
Lab Checkpoint 3: the
TCP sender
Now, in Lab 3, you’ll implement the other side of the connection. The
TCPSender is a tool that translates from an
outgoing byte stream to segments that will become the payloads
of unreliable datagrams.
modified files: tcp_sender.hh ,
tcp_sender.cc
sender part of TCP responsible for:
reading from a ByteStream (created and written to by
sender-side application)
turing the stream into a sequence of outgoing TCP segments.
TCP sender writes the sequence
number, the SYN flag, the
payload, and the FIN flag.
TCP sender only reads the segment that written by
the receiver: ackno and window_size.
TCPSender’s responsibility to:
Keep track of the receiver’s window (processing incoming
ackno and window_size)
Fill the window when possible
Reading from the ByteStream, creating new TCP segments (including
SYN and FIN flags if needed)
Sending them (until a. the window is full; b. the
ByteStream is empty)
Keep track of "outstanding" segments
have been sent but not yet acknowledged
Re-send "outstanding" segments if enough time passes since they
were sent (time out) and haven't been
asked yet
The basic principle is to send whatever the receiver will allow us to
send (filling the window), and keep retransmitting until the receiver
acknowledges each segment. This is called “automatic repeat request”
(ARQ).
tcp_sender.hh
TCPTimer
Handle outstanding segments via TCP Timer. The timer is
started when a segment is sent, and is reset when receiving an
acknowledgment. If the timer expires (indicating a potential loss of a
segment), the oldest unacknowledged segment is retransmitted, and the
timer's interval is adjusted according to the exponential backoff
algorithm (6(b)ii from lab3.pdf).
//! \brief TCP Timer classTCPTimer { private: //! true: the timer is started //! false: the timer is not started bool _start; //! initial time unsignedint _initial_transmission_time; //! transmission time unsignedint _transmission_time; //! retransmission timeout unsignedint _RTO;
public: //! number of consecutive retransmissions unsignedint num_of_retransmissions;
//! \brief The "sender" part of a TCP implementation. classTCPSender { private: ... /* ! added by myself */
// the latest ack no has been received uint64_t _ackno; // receiver’s window size size_t _window_size;
// sequence numbers are occupied by segments sent but not yet acked size_t _bytes_in_flight; // == _next_seqno - _ackno // timer used for controlling retransmissions TCPTimer timer; // segments in flight queue std::queue<TCPSegment> _segments_in_flight{};
// send segment that is not empty voidsend_segment(TCPSegment &seg);
public: ...
tcp_sender.cc
TCPSender::ack_received processes the data from the
received segment sent by the remote receiver.
graph LR
A["ack_received()"] -- update data --> C["full_window()"]
C --> D[timer]
D -- init --> F["start()"]
D -- end --> G["close()"]
C --> H[process states]
H --> C
H --> I["send_segment()"]
I --> H
// has been acked if (ackno_received <= _ackno) { return; }
// received new valid ackno _ackno = ackno_received;
// pop out all the segments that have been acked while (!_segments_in_flight.empty()) { TCPSegment seg = _segments_in_flight.front(); cerr << "seg.header().seqno.raw_value(): " << seg.header().seqno.raw_value() << ", length: " << static_cast<uint32_t>(seg.length_in_sequence_space()) << endl; cerr << "ackno.raw_value(): " << ackno.raw_value() << endl; if (seg.header().seqno.raw_value() + static_cast<uint32_t>(seg.length_in_sequence_space()) > ackno.raw_value()) { break; } _bytes_in_flight -= seg.length_in_sequence_space(); _segments_in_flight.pop(); logPrint(seg.payload().str(), "has been poped out of `_segments_in_flight`"); }
//! \brief create and send segments to fill as much of the window as possible voidTCPSender::fill_window(){ /* STATUS: CLOSED */ // waiting for stream to begin (no SYN sent) // definition: // next_seqno_absolute() == 0 if (next_seqno_absolute() == 0) { TCPSegment init_seg; init_seg.header().syn = true; init_seg.header().seqno = next_seqno(); send_segment(init_seg); logPrint(init_seg.payload().str(), "init_seg sent (SYN)"); }
/* STATUS: SYN_SENT */ // stream started but nothing acknowledged // definition: // next_seqno_absolute() > 0 // and next_seqno_absolute() == bytes_in_flight() if (next_seqno_absolute() > 0 && next_seqno_absolute() == bytes_in_flight()) { return; }
/* Conection has been built */
// If the receiver has announced a window size of zero, the fill window method should act like the window size is // *one*. size_t window_size = _window_size == 0 ? 1 : _window_size;
/* STATUS: SYN_ACKED */ // “stream ongoing” // if output haven't reach the end if (next_seqno_absolute() > bytes_in_flight() && !stream_in().eof()) { seg.payload() = Buffer(_stream.read(len)); // if all data has been read, set FIN value // second condition: "Don't add FIN if this would make the segment exceed the receiver's window" if (_stream.eof() && remain_size - seg.length_in_sequence_space() > 0) { seg.header().fin = true; } if (seg.length_in_sequence_space() == 0) return; // _stream has nothing to send send_segment(seg); }
/* STATUS: SYN_ACKED */ // “stream ongoing” (stream has reached EOF, but FIN flag hasn't been sent yet) elseif (stream_in().eof() && next_seqno_absolute() < stream_in().bytes_written() + 2) { seg.header().fin = true; send_segment(seg); }
/* STATUS: FIN_SENT & FIN_ACKED */ // have no business with `fill_window()` elseif (stream_in().eof() && next_seqno_absolute() == stream_in().bytes_written() + 2) { return; } // return; } }
When TCPSender::tick(const size_t ms_since_last_tick) is
called, it handles the milliseconds and maintains the timer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
//! \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){ // if timer is closed or not timeout, do nothing if (!timer.timeout(ms_since_last_tick)) { return; }
// if nothing can be retransmitted, close the timer if (_segments_in_flight.empty()) { timer.close(); return; }
// occurs timeout, retransmit the first segment in the `segments_in_filght` timer.restart(_window_size); segments_out().push(_segments_in_flight.front()); }
Total Test time (real) = 0.61 sec [100%] Built target check_lab3
Lab Checkpoint 4: the
summit (TCP in full)
Now, in Lab 4, you will make the overarching module, called
TCPConnection, that combines your
TCPSender and TCPReceiver and handles the
global housekeeping for the connection. The connection’s TCP segments
can be encapsulated into the payloads of user (TCP-in-UDP) or Internet
(TCP/IP) datagrams—letting your code talk to billions of other computers
on the Internet that speak the same TCP/IP language.
send_segments(): Handles the logic for sending
queued TCP segments from the sender's queue.
If the queue is empty, an empty segment is sent.
If the connection is in reset mode (_rst == true), the
segments are filled with the reset flag.
Segments are sent out until the queue is empty.
final_operation(): checks if the connection is done
and can be closed. (Corresponding to 5
The end of a TCP connection: consensus takes work)
If the inbound stream is fully assembled, but the outbound stream
has not yet ended, it disables lingering
(_linger_after_streams_finish = false).
If both the inbound and outbound streams are fully processed and
there are no bytes in flight, the connection is marked inactive if
lingering is disabled or if enough time has passed (10×RTT timeout)
since the last segment was received.
fill_queue(std::queue<TCPSegment>& stream_queue):
processes the queue of outgoing TCP segments from the sender.
Each segment is popped from the queue.
If the receiver has an acknowledgment number, it is added to the
segment header.
The segment is marked with the appropriate flags (RST, ACK, window
size).
The segment is pushed to the _segments_out queue for
transmission.
voidTCPConnection::segment_received(const TCPSegment &seg){ if (!_active) return;
_ms_since_last_segment_received = 0;
if (seg.header().rst) { set_rst(); return; }
_receiver.segment_received(seg);
// if the ACK feld is set, give that ackno and window size to TCPSender if (seg.header().ack) { _sender.ack_received(seg.header().ackno, seg.header().win); }
// first handshake if (seg.header().syn && !_established) { if (seg.header().ack) { _established = true; } else { _sender.fill_window(); } } elseif (seg.header().ack && !_established) { /* SYN-RECEIVE -> ESTABLISHED */ _established = true; }
// reply when segment received is not empty or _sender's queue is not empty if (seg.length_in_sequence_space() != 0 || !_sender.segments_out().empty()) { send_segments(); }
//! \param[in] ms_since_last_tick number of milliseconds since the last call to this method voidTCPConnection::tick(constsize_t ms_since_last_tick){ // DUMMY_CODE(ms_since_last_tick); _ms_since_last_segment_received += ms_since_last_tick;
if (_sender.tick(ms_since_last_tick)) { // if timeout if (_sender.consecutive_retransmissions() > TCPConfig::MAX_RETX_ATTEMPTS) { set_rst(); return; } send_segments(); }
voidTCPConnection::send_segments(){ std::queue<TCPSegment> &stream_queue = _sender.segments_out(); if (stream_queue.empty()) { _sender.send_empty_segment(); } if (_rst) { fill_queue(stream_queue); return; } while (!stream_queue.empty()) { fill_queue(stream_queue); } }
voidTCPConnection::final_operation(){ // If the inbound stream ends before the TCPConnection has reached EOF on its outbound stream, // this variable needs to be set to `false`. if (_receiver.stream_out().input_ended() && !_sender.stream_in().eof() && _sender.next_seqno_absolute() > 0) { // NOTE: different if (_linger_after_streams_finish) { } _linger_after_streams_finish = false; }
// At any point where prerequisites #1 through #3 are satisfied, the connection is “done” (and active() should return // false) if linger after streams finish is false. Otherwise you need to linger: the connection is only done after // enough time (10 × cfg.rt timeout) has elapsed since the last segment was received. Prereq # 1 The inbound stream // has been fully assembled and has ended. if (_receiver.unassembled_bytes() == 0 && _receiver.stream_out().eof()) { // Prereq # 2 and # 3 // The outbound stream has been ended by the local application and fully sent // (including the fact that it ended, i.e. a segment with `fin` ) to the remote peer. if (_sender.stream_in().eof() && _sender.fin_sent() && bytes_in_flight() == 0) { if (!_linger_after_streams_finish) { _active = false; } elseif (_ms_since_last_segment_received >= 10 * _cfg.rt_timeout) { _active = false; } } } }
voidTCPConnection::fill_queue(std::queue<TCPSegment> &stream_queue){ TCPSegment seg = stream_queue.front(); stream_queue.pop();
if (_receiver.ackno().has_value()) { seg.header().ack = true; seg.header().ackno = _receiver.ackno().value(); }
# Release with Address Sanitizer for memory error detection cmake .. -DCMAKE_BUILD_TYPE=RelASan
# optimized for performance, without debug information cmake .. -DCMAKE_BUILD_TYPE=Release
# includes debug symbols for better traceability of errors cmake .. -DCMAKE_BUILD_TYPE=Debug
# Run the TCP benchmarking application ./apps/tcp_benchmark
# Run the specified test ctest --output-on-failure -V -R t_ucR_128K_8K # e.g., this could be test #67
## 6 Testing # Play around with your custom TCP implementation, and capture traffic to debug with Wireshark. # You may need to install tshark (command-line version of Wireshark) first: sudo apt install tshark
# Capture traffic on interface 'tun144' and save to a file in raw format sudo tshark -Pw /tmp/debug.raw -i tun144
# Start the TCP server, listening on IP 169.254.144.9 and port 9090 ./apps/tcp_ipv4 -l 169.254.144.9 9090 # server
# Start the TCP client, sending traffic from IP 169.254.145.9 to the server at 169.254.144.9 on port 9090 ./apps/tcp_ipv4 -d tun145 -a 169.254.145.9 169.254.144.9 9090 # client
cs144@cs144vm:~/sponge/build$ curl google.com <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>301 Moved</TITLE></HEAD><BODY> <H1>301 Moved</H1> The document has moved <A HREF="http://www.google.com/">here</A>. </BODY></HTML> cs144@cs144vm:~/sponge/build$ ./apps/webget google.com / DEBUG: Connecting to 172.217.25.14:80... done. DEBUG: Outbound stream to 172.217.25.14:80 finished (56 bytes still in flight). DEBUG: Outbound stream to 172.217.25.14:80 has been fully acknowledged. HTTP/1.1 301 Moved Permanently Location: http://www.google.com/ Content-Type: text/html; charset=UTF-8 Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-4IjQrdJDtNiCR8ugZdgnwQ' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp Date: Wed, 21 Feb 2024 09:23:56 GMT Expires: Fri, 22 Mar 2024 09:23:56 GMT Cache-Control: public, max-age=2592000 Server: gws Content-Length: 219 X-XSS-Protection: 0 X-Frame-Options: SAMEORIGIN Connection: close
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>301 Moved</TITLE></HEAD><BODY> <H1>301 Moved</H1> The document has moved <A HREF="http://www.google.com/">here</A>. </BODY></HTML> DEBUG: Waiting for clean shutdown... DEBUG: Inbound stream from 172.217.25.14:80 finished cleanly. DEBUG: Waiting for lingering segments (e.g. retransmissions of FIN) from peer... DEBUG: TCP connection finished cleanly. done.
BufferList
Use BufferList from utl/buffer.hh instead
of std::deque<char> for _queue to store
the content. BufferList reduces overhead by managing larger
blocks of data more efficiently, minimizing the need for frequent
allocations and reducing memory fragmentation compared to handling
individual characters. It supports batch operations, allowing efficient
appending, copying, and removal of large data chunks, which is ideal for
stream processing. Additionally, peeking and removing data is faster, as
BufferList provides direct access to underlying buffers
without shifting elements in memory.
Using std::move when inserting a
std::string into a
std::map<size_t, std::string> is advantageous because
it avoids the overhead of copying the string's contents, instead
transferring ownership of its internal resources. This makes the
operation faster and more efficient, especially for large strings, as
moving a string simply transfers pointers rather than duplicating data.
Additionally, it ensures better resource management by leaving the
original string in a valid but empty state, reducing memory and CPU
usage compared to assignment.
Lab
Checkpoint 5: down the stack (the network interface)
In this lab, you’ll go down the stack and implement a network
interface: the bridge between Internet datagrams that travel the world,
and link-layer Ethernet frames that travel one hop.
//! remember the mapping between the sender’s IP address and Ethernet address for 30 seconds constsize_t _arp_entry_default_ttl = 30 * 1000;
//! If the network interface already sent an ARP request about the same IP address in the last five seconds, don’t send a second request constsize_t _arp_request_default_ttl = 5 * 1000;
//! \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();
// If the destination Ethernet address is already known, send it right away. if (arp_iter != _arp_table.end()) { // cerr << "the destination Ethernet address is already known, send it right away\n"; // Create an Ethernet frame EthernetFrame eth_frame;
// set the source and destination addresses eth_frame.header().dst = arp_iter->second.eth_addr; eth_frame.header().src = this->_ethernet_address; eth_frame.header().type = EthernetHeader::TYPE_IPv4;
// set the payload to be the serialized datagram eth_frame.payload() = dgram.serialize();
frames_out().push(eth_frame); } // If the destination Ethernet address is unknown, broadcast an ARP request elseif (_arp_requests_timer.find(next_hop_ip) == _arp_requests_timer.end()) { // cerr << "the destination Ethernet address is unknown, broadcast an ARP request\n"; ARPMessage arp_request;
// `arp_request.target_ethernet_address` is what we want arp_request.target_ip_address = next_hop_ip;
// queue the IP datagram so it can be sent after the ARP reply is received. _arp_requests_ip_datagram.emplace_back(std::pair<uint32_t, InternetDatagram>(next_hop_ip, dgram)); _arp_requests_timer[next_hop_ip] = NetworkInterface::_arp_request_default_ttl;
//! \param[in] frame the incoming Ethernet frame optional<InternetDatagram> NetworkInterface::recv_frame(const EthernetFrame &frame){ // cerr << "[DEBUG] recv_frame \n"; // ignore any frames not destined for the network interface // the Ethernet destination is either the broadcast address or the interface’s own Ethernet address stored in the // `_ethernet_address` member variable if (frame.header().dst != this->_ethernet_address && frame.header().dst != ETHERNET_BROADCAST) { return {}; }
if (frame.header().type == EthernetHeader::TYPE_IPv4) { // parse the payload as an InternetDatagram and, if successful (meaning the parse() method returned // ParseResult::NoError), return the resulting InternetDatagram to the caller. InternetDatagram dgram; if (dgram.parse(frame.payload()) == ParseResult::NoError) { return dgram; } } // Parse the payload as an ARPMessage and, if successful, remember the mapping elseif (frame.header().type == EthernetHeader::TYPE_ARP) { ARPMessage arp_msg; if (arp_msg.parse(frame.payload()) == ParseResult::NoError) { constuint32_t &src_ip_addr = arp_msg.sender_ip_address; constuint32_t &dst_ip_addr = arp_msg.target_ip_address; const EthernetAddress &src_eth_addr = arp_msg.sender_ethernet_address; const EthernetAddress &dst_eth_addr = arp_msg.target_ethernet_address;
// if successful, remember the mapping between the sender’s IP address and Ethernet address for 30 seconds // (Learn mappings from both requests and replies.) if (is_valid_arp_request || is_valid_arp_reply) { _arp_table[src_ip_addr] = {src_eth_addr, NetworkInterface::_arp_entry_default_ttl};
// send the IP datagram in the queue std::list<std::pair<uint32_t, InternetDatagram>>::iterator iter = _arp_requests_ip_datagram.begin(); while (iter != _arp_requests_ip_datagram.end()) { if (iter->first == src_ip_addr) { send_datagram(iter->second, Address::from_ipv4_numeric(src_ip_addr)); iter = _arp_requests_ip_datagram.erase(iter); } else { iter++; } } _arp_requests_timer.erase(src_ip_addr); }
// In addition, if it’s an ARP request asking for our IP address, send an appropriate ARP reply. if (is_valid_arp_request) { // cerr << " [DEBUG] is_valid_arp_request \n"; ARPMessage arp_reply; arp_reply.sender_ip_address = this->_ip_address.ipv4_numeric(); arp_reply.sender_ethernet_address = this->_ethernet_address; arp_reply.target_ip_address = src_ip_addr; arp_reply.target_ethernet_address = src_eth_addr; arp_reply.opcode = ARPMessage::OPCODE_REPLY;
//! \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){ /* ARP table */ // Expire any IP-to-Ethernet mappings that have expired. for (std::map<uint32_t, ARP_Entry>::iterator iter = _arp_table.begin(); iter != _arp_table.end(); /* removed (Segment Fault) */) { if (iter->second.ttl <= ms_since_last_tick) { iter = _arp_table.erase(iter); } else { iter->second.ttl -= ms_since_last_tick; iter++; // increment at this line } }
/* ARP request */ for (auto iter = _arp_requests_timer.begin(); iter != _arp_requests_timer.end(); ++iter) { // Resend ARP request for expired ARP request if (iter->second <= ms_since_last_tick) { // cerr << "Resend ARP request for expired ARP request. [ip=" // << Address::from_ipv4_numeric(iter->first).to_string() << "]\n"; ARPMessage arp_request;
cs144@cs144vm:~/sponge/build$ ctest --output-on-failure -V -R "^arp" UpdateCTestConfiguration from :/home/cs144/sponge/build/DartConfiguration.tcl UpdateCTestConfiguration from :/home/cs144/sponge/build/DartConfiguration.tcl Test project /home/cs144/sponge/build Constructing a list of tests Done constructing a list of tests Updating test list for fixtures Added 0 tests to meet fixture requirements Checking test dependency graph... Checking test dependency graph end test 32 Start 32: arp_network_interface
32: Test command: /home/cs144/sponge/build/tests/net_interface 32: Test timeout computed to be: 10000000 32: DEBUG: Network interface has Ethernet address 62:9f:b7:22:23:5c and IP address 4.3.2.1 32: DEBUG: Network interface has Ethernet address 1e:fb:05:3f:41:b9 and IP address 5.5.5.5 32: DEBUG: Network interface has Ethernet address ba:a7:34:13:93:2d and IP address 5.5.5.5 32: DEBUG: Network interface has Ethernet address 22:a9:60:f2:a1:dd and IP address 1.2.3.4 32: DEBUG: Network interface has Ethernet address be:3f:99:d2:5b:43 and IP address 4.3.2.1 32: DEBUG: Network interface has Ethernet address 26:15:35:db:24:9e and IP address 10.0.0.1 1/1 Test #32: arp_network_interface ............ Passed 0.01 sec
The following tests passed: arp_network_interface
100% tests passed, 0 tests failed out of 1
Lab Checkpoint 6:
building an IP router
In this lab checkpoint, you’ll implement an IP
router on top of your existing NetworkInterface. A router has
several network interfaces, and can
receive Internet datagrams on any of them. The router’s
job is to forward the datagrams it gets according to the routing
table: a list of rules that tells the router, for any given
datagram,
What interface to send it out
The IP address of the next hop
router.hh
router_entry: Represents an entry in the router's
routing table.
_routing_table: A set that stores all routing
entries for the router.
Each entry is sorted according to the router_entrycomparison operator.
This ensures the most specific routes are checked
first when forwarding a datagram.
check_prefix(uint32_t entry_prefix, uint32_t compared_prefix, uint8_t prefix_length):
Checks if the destination IP matches the network defined by the routing
table entry.
//! \param[in] dgram The datagram to be routed voidRouter::route_one_datagram(InternetDatagram &dgram){ // The router decrements the datagram’s TTL (time to live). // If the TTL was zero already, or hits zero after the decrement, the router should drop the datagram. if (dgram.header().ttl <= 1) { return; } else { dgram.header().ttl--; }
// bool is_found = false;
for (auto &entry : _routing_table) { if (check_prefix(entry.route_prefix, dgram.header().dst, entry.prefix_length)) { cerr << "[DEBUG] dst: " << Address::from_ipv4_numeric(dgram.header().dst).to_string() << "/" << int(entry.prefix_length) << " -> router: " << Address::from_ipv4_numeric(entry.route_prefix).to_string() << endl; // If the router is directly* attached to the network in question, the next hop will be an empty optional. // In that case, the next hop is the datagram’s destination address. if (!entry.next_hop.has_value()) { interface(entry.interface_num).send_datagram(dgram, Address::from_ipv4_numeric(dgram.header().dst));
cs144@cs144vm:~/sponge/build$ make check_lab6 [100%] Testing Lab 6... Test project /home/cs144/sponge/build Start 32: arp_network_interface 1/2 Test #32: arp_network_interface ............ Passed 0.00 sec Start 33: router_test 2/2 Test #33: router_test ...................... Passed 0.01 sec
100% tests passed, 0 tests failed out of 2
Total Test time (real) = 0.01 sec [100%] Built target check_lab6
参考
其他
Project Description
Implemented a series of labs covering key aspects of networking,
focusing on building reliable transport protocols and understanding
TCP/IP stack concepts. The project demonstrates proficiency in socket
programming, byte stream manipulation, TCP segment handling, and
connection management.
Technical Keywords
Networking
TCP/IP
C++
Key Achievements and
Technologies
Lab 1: Implemented a ByteStream
and basic socket communication to fetch web pages using low-level TCP
connections. Developed an understanding of TCP socket programming
fundamentals.
Utilized C++ sockets for network
communication.
Implemented stream reassembly logic for ordered
byte stream delivery.
Lab 2: Built the TCP Receiver,
handling incoming TCP segments and reconstructing the byte stream by
managing sequence numbers, acknowledgment numbers, and window sizes.
Managed 64-bit sequence numbers and 32-bit TCP
sequence number wrapping.
Developed logic to track TCP window size and
maintain in-order stream reassembly.
Lab 3: Developed the TCP Sender,
responsible for transforming byte streams into TCP segments and ensuring
reliable transmission through retransmission and acknowledgment
handling.
Implemented Automatic Repeat Request (ARQ) and
exponential backoff for retransmissions.
Handled TCP segment creation, including SYN, ACK,
and FIN flags.
Lab 4: Integrated both the sender and receiver into
a complete TCP connection, managing the lifecycle of
TCP connections, including connection establishment, data transmission,
and teardown.
Implemented a full TCP connection state machine,
tracking connection state and handling connection errors (RST,
FIN).
Applied TCP flow control and congestion
control mechanisms to ensure reliable and efficient data
transmission.
Lab 5: Extended the TCP implementation with
timeout mechanisms and optimizations for handling large
data transfers with minimal packet loss.
Fine-tuned the retransmission timeout (RTO)
mechanism.
Enhanced error handling and packet loss
recovery using adaptive retransmission strategies.
Lab 6: Tested the full TCP stack implementation
with real-world benchmarking tools, including
Wireshark for packet analysis and tuning the system for
optimal performance under varying network conditions.
Used tshark to capture and analyze network
traffic.
Achieved 100% pass rate on extensive test suites,
validating the robustness and correctness of the implementation.
Each lab focused on gradually constructing a reliable,
efficient, and standard-compliant TCP stack, culminating in a
full TCP-over-UDP and TCP/IP solution
that interoperates with existing network stacks.