對(duì)應(yīng)課程視頻: 【計(jì)算機(jī)網(wǎng)絡(luò)】 斯坦福大學(xué)CS144課程
Lab Six 對(duì)應(yīng)的PDF: Lab Checkpoint 5: building an IP router
引言
在本實(shí)驗(yàn)中,你將在現(xiàn)有的NetworkInterface
基礎(chǔ)上實(shí)現(xiàn)一個(gè)IP路由器,從而結(jié)束本課程。路由器有幾個(gè)網(wǎng)絡(luò)接口,可以在其中任何一個(gè)接口上接收互聯(lián)網(wǎng)數(shù)據(jù)報(bào)。路由器的工作是根據(jù)路由表轉(zhuǎn)發(fā)它得到的數(shù)據(jù)報(bào):一個(gè)規(guī)則列表,它告訴路由器,對(duì)于任何給定的數(shù)據(jù)報(bào):
- 發(fā)送到哪個(gè)接口;
- 下一跳的IP地址 ;
你的工作是實(shí)現(xiàn)一個(gè)路由器,它可以為任何給定的數(shù)據(jù)報(bào)計(jì)算出這兩件事。(你不需要實(shí)現(xiàn)設(shè)置路由表的算法,例如RIP、OSPF、BGP或SDN控制器,只需要實(shí)現(xiàn)跟隨路由表的算法)。
你對(duì)路由器的實(shí)現(xiàn)將使用帶有新的Router
類的Sponge庫(kù),以及在模擬網(wǎng)絡(luò)中檢查你的路由器功能的測(cè)試。實(shí)驗(yàn)6建立在你在實(shí)驗(yàn)5中對(duì)NetworkInterface
的實(shí)現(xiàn)之上,但不使用你在實(shí)驗(yàn)0-4中實(shí)現(xiàn)的TCP棧。IP路由器不需要知道任何關(guān)于TCP、ARP或以太網(wǎng)的信息(僅限IP)。我們希望你的實(shí)現(xiàn)將需要大約25-30行的代碼。
圖1:路由器包含多個(gè)網(wǎng)絡(luò)接口,可以在其中任何一個(gè)接口上接收IP數(shù)據(jù)報(bào)。路由器將接收到的任何數(shù)據(jù)報(bào)轉(zhuǎn)發(fā)到相應(yīng)出站接口上的下一跳,路由表告訴路由器如何做出這個(gè)決定。
路由器的實(shí)現(xiàn)
AsyncNetworkInterface:
- 它是對(duì) NetworkInterface 類的包裝,用于使主機(jī)端接口變成異步的。
- 在原始的 NetworkInterface 類的基礎(chǔ)上,AsyncNetworkInterface 將接收到的數(shù)據(jù)報(bào)保存在隊(duì)列中,而不是立即返回給調(diào)用者,以便稍后檢索。
- 同時(shí),AsyncNetworkInterface 在其他方面與底層的 NetworkInterface 實(shí)現(xiàn)完全相同。
class AsyncNetworkInterface : public NetworkInterface {
std::queue<InternetDatagram> _datagrams_out{};
public:
using NetworkInterface::NetworkInterface;
//! Construct from a NetworkInterface
AsyncNetworkInterface(NetworkInterface &&interface) : NetworkInterface(interface) {}
//! \brief Receives and Ethernet frame and responds appropriately.
//! - If type is IPv4, pushes to the `datagrams_out` queue for later retrieval by the owner.
//! - If type is ARP request, learn a mapping from the "sender" fields, and send an ARP reply.
//! - If type is ARP reply, learn a mapping from the "target" fields.
//!
//! \param[in] frame the incoming Ethernet frame
void recv_frame(const EthernetFrame &frame) {
auto optional_dgram = NetworkInterface::recv_frame(frame);
// 只會(huì)將IPV4數(shù)據(jù)報(bào)放入數(shù)據(jù)報(bào)接收隊(duì)列中
if (optional_dgram.has_value()) {
_datagrams_out.push(std::move(optional_dgram.value()));
}
};
//! Access queue of Internet datagrams that have been received
std::queue<InternetDatagram> &datagrams_out() { return _datagrams_out; }
};
這里的 Router 實(shí)現(xiàn)比較簡(jiǎn)單,只需實(shí)現(xiàn)一下 IP 最長(zhǎng)匹配并將數(shù)據(jù)包轉(zhuǎn)發(fā)即可:
Router.hh:
//! \brief A router that has multiple network interfaces and
//! performs longest-prefix-match routing between them.
class Router {
//! The router's collection of network interfaces
// 當(dāng)前路由器的網(wǎng)絡(luò)接口集合
std::vector<AsyncNetworkInterface> _interfaces{};
//! Send a single datagram from the appropriate outbound interface to the next hop,
//! as specified by the route with the longest prefix_length that matches the
//! datagram's destination address.
// 路由一個(gè)IP數(shù)據(jù)報(bào)
void route_one_datagram(InternetDatagram &dgram);
// 路由表?xiàng)l目
struct RouterTableEntry {
// 路由前綴
const uint32_t route_prefix;
// 前綴長(zhǎng)度
const uint8_t prefix_length;
// 下一跳的IP地址
const std::optional<Address> next_hop;
// 對(duì)應(yīng)哪一個(gè)網(wǎng)絡(luò)接口
const size_t interface_idx;
};
// 路由表
std::vector<RouterTableEntry> _router_table{};
public:
//! Add an interface to the router
//! \param[in] interface an already-constructed network interface
//! \returns The index of the interface after it has been added to the router
// 向路由表添加網(wǎng)絡(luò)接口
size_t add_interface(AsyncNetworkInterface &&interface) {
_interfaces.push_back(std::move(interface));
return _interfaces.size() - 1;
}
//! Access an interface by index -- 根據(jù)索引獲取某一個(gè)網(wǎng)絡(luò)接口
AsyncNetworkInterface &interface(const size_t N) { return _interfaces.at(N); }
//! Add a route (a forwarding rule)
// 增加路由條目
void add_route(const uint32_t route_prefix,
const uint8_t prefix_length,
const std::optional<Address> next_hop,
const size_t interface_num);
//! Route packets between the interfaces
void route();
};
Router.cc:
- add_route : 向路由表中添加路由條目
// 向路由表中添加路由條目
void Router::add_route(const uint32_t route_prefix,
const uint8_t prefix_length,
const optional<Address> next_hop,
const size_t interface_num) {
cerr << "DEBUG: adding route " << Address::from_ipv4_numeric(route_prefix).ip() << "/" << int(prefix_length)
<< " => " << (next_hop.has_value() ? next_hop->ip() : "(direct)") << " on interface " << interface_num << "\n";
_router_table.push_back({route_prefix, prefix_length, next_hop, interface_num});
}
- route_one_datagram: 根據(jù)路由表完成當(dāng)前IP數(shù)據(jù)報(bào)的路由工作
//! \param[in] dgram The datagram to be routed
// 根據(jù)路由表進(jìn)行路由
void Router::route_one_datagram(InternetDatagram &dgram) {
// 獲取目的ip地址
const uint32_t dst_ip_addr = dgram.header().dst;
auto max_matched_entry = _router_table.end();
// 開始查詢
for (auto router_entry_iter = _router_table.begin(); router_entry_iter != _router_table.end();
router_entry_iter++) {
// 如果前綴匹配匹配長(zhǎng)度為 0,或者前綴匹配相同
if (router_entry_iter->prefix_length == 0 ||
(router_entry_iter->route_prefix ^ dst_ip_addr) >> (32 - router_entry_iter->prefix_length) == 0) {
// 如果條件符合,則更新最匹配的條目
if (max_matched_entry == _router_table.end() ||
max_matched_entry->prefix_length < router_entry_iter->prefix_length)
max_matched_entry = router_entry_iter;
}
}
// 將數(shù)據(jù)包 TTL 減去1
// 如果存在最匹配的,并且數(shù)據(jù)包仍然存活,則將其轉(zhuǎn)發(fā)
if (max_matched_entry != _router_table.end() && dgram.header().ttl-- > 1) {
// 獲取下一條IP地址
const optional<Address> next_hop = max_matched_entry->next_hop;
// 獲取對(duì)應(yīng)的網(wǎng)絡(luò)接口
AsyncNetworkInterface &interface = _interfaces[max_matched_entry->interface_idx];
// 目標(biāo)主機(jī)是否位于與路由器相同的網(wǎng)絡(luò)中。
// 在這種情況下,下一跳字段可能為空,因?yàn)槟繕?biāo)主機(jī)可以直接通過局域網(wǎng)訪問,無需經(jīng)過路由器。
if (next_hop.has_value())
// 交給NetworkInterface,將這個(gè)數(shù)據(jù)報(bào)發(fā)送出去
interface.send_datagram(dgram, next_hop.value());
else
// 目的主機(jī)與路由器位于相同的網(wǎng)絡(luò)中
interface.send_datagram(dgram, Address::from_ipv4_numeric(dst_ip_addr));
}
// 其他情況下則丟棄該數(shù)據(jù)包
}
上面的代碼中,next_hop.has_value()
為 false
表示沒有下一跳(next hop)地址,即無法找到用于轉(zhuǎn)發(fā)數(shù)據(jù)包的下一跳。這可能發(fā)生在以下情況下:
-
直接連接目標(biāo)主機(jī): 路由表中可能存在直接連接目標(biāo)主機(jī)的路由條目,也就是目標(biāo)主機(jī)位于與路由器相同的網(wǎng)絡(luò)中。在這種情況下,下一跳字段可能為空,因?yàn)槟繕?biāo)主機(jī)可以直接通過局域網(wǎng)訪問,無需經(jīng)過路由器。
-
默認(rèn)路由: 路由表中通常會(huì)包含默認(rèn)路由(default route),也稱為默認(rèn)網(wǎng)關(guān)(default gateway)。默認(rèn)路由是指當(dāng)沒有更精確的路由匹配時(shí),所有未知目標(biāo)IP地址的數(shù)據(jù)包將會(huì)通過默認(rèn)路由進(jìn)行轉(zhuǎn)發(fā)。在這種情況下,下一跳字段可能為空,因?yàn)槟J(rèn)路由指定了一個(gè)特定的網(wǎng)絡(luò)接口,將數(shù)據(jù)包發(fā)送到該接口,由默認(rèn)網(wǎng)關(guān)負(fù)責(zé)將數(shù)據(jù)包轉(zhuǎn)發(fā)到外部網(wǎng)絡(luò)。
需要注意的是,在實(shí)際網(wǎng)絡(luò)中,路由表會(huì)根據(jù)網(wǎng)絡(luò)拓?fù)浜吐酚刹呗赃M(jìn)行配置,以確保數(shù)據(jù)包能夠正確地轉(zhuǎn)發(fā)到目標(biāo)。路由表中的路由條目根據(jù)目標(biāo)網(wǎng)絡(luò)地址的前綴匹配來確定數(shù)據(jù)包的轉(zhuǎn)發(fā)規(guī)則。當(dāng)無法找到匹配的路由條目時(shí),數(shù)據(jù)包將根據(jù)默認(rèn)路由進(jìn)行轉(zhuǎn)發(fā),或者如果沒有默認(rèn)路由,則會(huì)被丟棄。
- Route: AsyncNetworkInterface會(huì)將接收到的IP數(shù)據(jù)報(bào)暫存在隊(duì)列中,由Route方法負(fù)責(zé)從隊(duì)列取出并進(jìn)行路由
void Router::route() {
// Go through all the interfaces, and route every incoming datagram to its proper outgoing interface.
// 依次遍歷當(dāng)前路由器內(nèi)部每個(gè)網(wǎng)絡(luò)接口,依次取出每個(gè)AsyncNetworkInterface的待傳輸隊(duì)列datagrams_out
for (auto &interface : _interfaces) {
auto &queue = interface.datagrams_out();
// 如果待路由隊(duì)列不為空,則依次取出進(jìn)行路由
while (not queue.empty()) {
route_one_datagram(queue.front());
queue.pop();
}
}
}
測(cè)試
這是 CS144 的測(cè)試網(wǎng)絡(luò)拓?fù)洌?br>
測(cè)試結(jié)果:文章來源:http://www.zghlxwxcb.cn/news/detail-629753.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-629753.html
到了這里,關(guān)于CS 144 Lab Six -- building an IP router的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!