Skip to content

Commit d5cb1d2

Browse files
committed
CS 144 Checkpoint 5: the IP router
1 parent 1bf21b1 commit d5cb1d2

8 files changed

+472
-3
lines changed

etc/tests.cmake

+4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ ttest(send_extra)
5656

5757
ttest(net_interface)
5858

59+
ttest(router)
60+
5961
add_custom_target (check0 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --stop-on-failure --timeout 12 -R 'webget|^byte_stream_')
6062

6163
add_custom_target (check_webget COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 12 -R 'webget')
@@ -68,6 +70,8 @@ add_custom_target (check3 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --s
6870

6971
add_custom_target (check4 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --stop-on-failure --timeout 12 -R '^net_interface')
7072

73+
add_custom_target (check5 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --stop-on-failure --timeout 12 -R '^net_interface|^router')
74+
7175
###
7276

7377
add_custom_target (speed COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 12 -R '_speed_test')

scripts/lines-of-code

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ modules = [['byte_stream', 'ByteStream'],
1111
['tcp_receiver', 'TCPReceiver'],
1212
['wrapping_integers', 'Wrap32'],
1313
['tcp_sender', 'TCPSender'],
14-
['network_interface', 'NetworkInterface']]
14+
['network_interface', 'NetworkInterface'],
15+
['router', 'Router']]
1516
longest_module_length = max([len(x[1]) for x in modules])
1617

1718

src/router.cc

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#include "router.hh"
2+
3+
#include <iostream>
4+
#include <limits>
5+
6+
using namespace std;
7+
8+
// route_prefix: The "up-to-32-bit" IPv4 address prefix to match the datagram's destination address against
9+
// prefix_length: For this route to be applicable, how many high-order (most-significant) bits of
10+
// the route_prefix will need to match the corresponding bits of the datagram's destination address?
11+
// next_hop: The IP address of the next hop. Will be empty if the network is directly attached to the router (in
12+
// which case, the next hop address should be the datagram's final destination).
13+
// interface_num: The index of the interface to send the datagram out on.
14+
void Router::add_route( const uint32_t route_prefix,
15+
const uint8_t prefix_length,
16+
const optional<Address> next_hop,
17+
const size_t interface_num )
18+
{
19+
cerr << "DEBUG: adding route " << Address::from_ipv4_numeric( route_prefix ).ip() << "/"
20+
<< static_cast<int>( prefix_length ) << " => " << ( next_hop.has_value() ? next_hop->ip() : "(direct)" )
21+
<< " on interface " << interface_num << "\n";
22+
23+
(void)route_prefix;
24+
(void)prefix_length;
25+
(void)next_hop;
26+
(void)interface_num;
27+
}
28+
29+
void Router::route() {}

src/router.hh

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#pragma once
2+
3+
#include "network_interface.hh"
4+
5+
#include <optional>
6+
#include <queue>
7+
8+
// A wrapper for NetworkInterface that makes the host-side
9+
// interface asynchronous: instead of returning received datagrams
10+
// immediately (from the `recv_frame` method), it stores them for
11+
// later retrieval. Otherwise, behaves identically to the underlying
12+
// implementation of NetworkInterface.
13+
class AsyncNetworkInterface : public NetworkInterface
14+
{
15+
std::queue<InternetDatagram> datagrams_in_ {};
16+
17+
public:
18+
using NetworkInterface::NetworkInterface;
19+
20+
// Construct from a NetworkInterface
21+
explicit 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+
{
32+
auto optional_dgram = NetworkInterface::recv_frame( frame );
33+
if ( optional_dgram.has_value() ) {
34+
datagrams_in_.push( std::move( optional_dgram.value() ) );
35+
}
36+
};
37+
38+
// Access queue of Internet datagrams that have been received
39+
std::optional<InternetDatagram> maybe_receive()
40+
{
41+
if ( datagrams_in_.empty() ) {
42+
return {};
43+
}
44+
45+
InternetDatagram datagram = std::move( datagrams_in_.front() );
46+
datagrams_in_.pop();
47+
return datagram;
48+
}
49+
};
50+
51+
// A router that has multiple network interfaces and
52+
// performs longest-prefix-match routing between them.
53+
class Router
54+
{
55+
// The router's collection of network interfaces
56+
std::vector<AsyncNetworkInterface> interfaces_ {};
57+
58+
public:
59+
// Add an interface to the router
60+
// interface: an already-constructed network interface
61+
// returns the index of the interface after it has been added to the router
62+
size_t add_interface( AsyncNetworkInterface&& interface )
63+
{
64+
interfaces_.push_back( std::move( interface ) );
65+
return interfaces_.size() - 1;
66+
}
67+
68+
// Access an interface by index
69+
AsyncNetworkInterface& interface( size_t N ) { return interfaces_.at( N ); }
70+
71+
// Add a route (a forwarding rule)
72+
void add_route( uint32_t route_prefix,
73+
uint8_t prefix_length,
74+
std::optional<Address> next_hop,
75+
size_t interface_num );
76+
77+
// Route packets between the interfaces. For each interface, use the
78+
// maybe_receive() method to consume every incoming datagram and
79+
// send it on one of interfaces to the correct next hop. The router
80+
// chooses the outbound interface and next-hop as specified by the
81+
// route with the longest prefix_length that matches the datagram's
82+
// destination address.
83+
void route();
84+
};

tests/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,7 @@ add_test_exec(send_extra)
6969

7070
add_test_exec(net_interface)
7171

72+
add_test_exec(router)
73+
7274
add_speed_test(byte_stream_speed_test)
7375
add_speed_test(reassembler_speed_test)

tests/net_interface.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ InternetDatagram make_datagram( const string& src_ip, const string& dst_ip ) //
2727
dgram.header.src = Address( src_ip, 0 ).ipv4_numeric();
2828
dgram.header.dst = Address( dst_ip, 0 ).ipv4_numeric();
2929
dgram.payload.emplace_back( "hello" );
30-
dgram.header.len = static_cast<uint64_t>( dgram.header.hlen ) * 4 + dgram.payload.size();
30+
dgram.header.len = static_cast<uint64_t>( dgram.header.hlen ) * 4 + dgram.payload.front().size();
3131
dgram.header.compute_checksum();
3232
return dgram;
3333
}

tests/network_interface_test_harness.hh

+10-1
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,23 @@ struct Tick : public Action<NetworkInterface>
135135
explicit Tick( const size_t ms ) : _ms( ms ) {}
136136
};
137137

138+
inline std::string concat( std::vector<Buffer>& buffers )
139+
{
140+
return std::accumulate(
141+
buffers.begin(), buffers.end(), std::string {}, []( const std::string& x, const Buffer& y ) {
142+
return x + static_cast<std::string>( y );
143+
} );
144+
}
145+
138146
inline std::string summary( const EthernetFrame& frame )
139147
{
140148
std::string out = frame.header.to_string() + ", payload: ";
141149
switch ( frame.header.type ) {
142150
case EthernetHeader::TYPE_IPv4: {
143151
InternetDatagram dgram;
144152
if ( parse( dgram, frame.payload ) ) {
145-
out.append( "IPv4: " + dgram.header.to_string() );
153+
out.append( "IPv4: " + dgram.header.to_string() + " payload=\""
154+
+ Printer::prettify( concat( dgram.payload ) ) + "\"" );
146155
} else {
147156
out.append( "bad IPv4 datagram" );
148157
}

0 commit comments

Comments
 (0)