CS144--lab5笔记

CS144——Lab5笔记

Getting started

首先是版本控制操作,从lab4分支创建用于lab5开发的分支,我是在Clion的Git图形操作界面设定的。然后按照文档中的的介绍,从远程仓库拉取lab5的实验内容

//在新分支dev-lab5下
git fetch
git merge origin/lab5-startercode
make -j4

会弹出一个询问界面,让你提交此次合并到你本地仓库分支的说明commit,可以不管,直接ctrl+X键。
首先来看一下整个CS144实验的人物安排,如下图:
在这里插入图片描述

红色方框中的就是本次实验需要实现的。

TCPsegment传输方式

传输TCPsegment到远端,有以下几种方式:

  • TCP-in-UDP-in-IP:TCPsegment作为UDP报文的载荷,通常使用linux提供的网络接口(UDPsocket),让内核直接构造UPD报文头、IP报文头和以太网报文头,并将构造出的数据包传输到下一层。由于是在内核中完成,linux kernel保证了 唯一的本地地址和端口、目的地址和端口的组合,且保证 不同进程隔离
  • TCP-in-IP:通常可以直接将TCPsegment作为IP报文的载荷,这种称为“TCP/IP”。如果想直接构造IP报文,需要使用Linux提供的接口(TUN虚拟网络设备),接着linux kernel负责构造以太网帧以及发送。

这部分参考以下Lab4中使用到的。请阅读以下头文件和源文件:tcp_helpers/ipv4_datagram.hh、tcp_helpers/ipv4_datagram.cc、tcp_helpers/tcp_over_ip.cc

  • TCP-in-IP-in-Ethernet:前两种实现以来linux的网络协议栈。向TUN网络虚拟设备写入IP报文时,Linux必须构造一个IP报文作为载荷的链路层(以太网)帧。所以Linux必须指导下一跳的以太网目的地址、IP地址,如果不知道这种映射,则在网络中广播探测请求下一跳的IP、以太网帧地址。此功能通常叫网络接口(network interface,或者适配器,名字一般是:eth0,eht1,wlan0),它将要 输出流的IP报文 转换成以太网帧,接着将帧发送给 TAP虚拟网络设备,具体发送就由此设备完成

网络接口的功能: 查找(缓存)下一跳的IP地址和以太网地址,这个协议通常叫 Address Resolution Protocol,简称 ARP

本次Lab5实验就是完成该网络接口。

The Address Resolution Protocol 地址解析协议

简略了解一下ARP:
主机或路由不具备链路层地址( MAC地址),但其对应的网络接口具有该地址。某个网络接口要向另外一个目的网络接口发送以太网帧时,发送的网络接口需要将目的网络接口的MAC地址插入该帧的目的mac地址中,以太帧的结构如下:
在这里插入图片描述

其中 EtherType0x800时指IPv4数据报,0x806为ARP数据报

并将该帧发送到所在局域网上。网络接口可能收到广播的帧,其将检查和丢弃 帧的目的MAC地址与自己不匹配的以太网帧
为什么网络接口除了有 网络层地址(IP),还有 链路层地址(MAC地址)?

局域网是为了 任意网络层协议设计的,范围不止用于IP和Internet
如果只是用IP地址,每一次移动或者重启,都需要 重新配置地址

但拥有两种地址,就需要互相转化,由ARP实现,类似DNS服务,ARP只能为 在同一个子网上的主机或路由接口 解析IP地址。每一个主机和路由中都有一个ARP缓存表,该表包含了 IP地址MAC地址 的映射关系,还可能有其他选项比如接口名字、网络类型等。本次中由于需要额外记录删除表中项目的时间,所以添加一个最大存活时间 TTL,表示从表中删除该映射关系的时间,如图:

IP地址MAC地址TTL
192.168.4.300:50:56:f1:23:1d13:56:30
192.168.4.900:23:64:d2:f1:2300:23:36

如果缓存表中有目的IP地址,将很容易找到对应的MAC地址,否则将构造一个ARP请求报文(和ARP报文一样格式,发送端的IP地址和MAC地址填自己的,目的端的IP地址填入,MAC地址全为0),然后在该 子网上广播。只有IP地址相同的网络接口相应。该网络接口自己缓存一份发送端的IP地址和MAC地址,接着 单播方式发送ARP报文给前面发送此请求的网络接口,该ARP响应报文中包含自己的MAC地址。
由于 ARP协议不提供对网络上的ARP回复进行身份验证,导致可以实施 ARP欺骗攻击,例如中间人或者DOS攻击。

详情查阅RFC826

实现 Network Interface

在arp缓存表中,每个IP地址映射一个MAC地址,但由于额外需要增加一个TTL记录,所以可以先将MAC地址与TTL组成一个单独数据结构,如下:

struct ARPEntry{
EthernetAddress eth_addr;
size_t 
};

此数据结构将作为NetworkInterface类中private属性的数据成员声明。
此数据成员为ARP缓存表中的项,所以再额外添加三个数据成员和一个公共函数:

  • _arp_table:ARP缓存表,用来查询IP地址到MAC地址的映射,其中MAC地址和TTL构成ARPEntry

每个ARPEntry中TTL为30s

  • _waiting_arp_response_ip_addr:保存已经发送了ARP请求的IP地址,后面跟着一个TTL

此TTL为5s

  • _waiting_arp_internet_datagram:保存等待ARP返回报文的IP报文。收到返回相应报文,网络接口才知道这些IP报文要发送的目的地MAC地址

实现注意事项:

  • ARPEntry中的 TTL为30s,到期了就要将其ARP缓存表中删除
  • 发送IP报文时,没在ARP缓存表中发现MAC地址,立即广播发送ARP请求报文,同时将此IP报文缓存记录,直到获取到目的地MAC地址后发送
  • 同一ARP请求5s内没有相应,则需重新发送
  • 不同IP地址的ARP请求报文的发送间隔不能超过5s
  • 当网络接口收到一个链路层(以太网)帧:
    • 立即丢弃目的地MAC地址和本机不同的帧
    • 除了ARP协议需要比较自己的IP地址外,不要再其他任何地方进行IP比较,因为网络接口位于链路层
    • 对于APR请求报文的构造,目的地MAC地址为全零,收到ARP请求报文时也需要忽略此项(ARPMessage::target_ethernet_address
    • 无论是 ARP请求包还是 ARP响应包均可以更新当前的ARP缓存表

头文件中代码:

  //! ARP 条目
    struct ARPEntry {
        EthernetAddress eth_addr;  //!< 以太网帧地址
        size_t ttl;                //!< 最大存活时间
    };
    //! 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{};

    //! 内部维护的arp表
    std::unordered_map<uint32_t, ARPEntry> _arp_table{};

    //! 正在查询的ARP报文。如果发送了ARP请求报文后,在TTL时间内没收到返回响应报文,则丢弃该IP报文
    std::unordered_map<uint32_t, size_t> _waiting_arp_response_ip_addr{};

    //! 等待ARP报文返回的待处理IP报文,每一个IP地址都映射到 IP报文等待发送列表
    std::unordered_map<uint32_t, std::list<std::pair<Address, InternetDatagram>>> _waiting_internet_datagram{};

    //! \brief网络接口必须具备发送以太网帧的功能
    void _send(const EthernetAddress &det, const uint16_t type, BufferList &&payload);

  public:
    //! ARP条目默认最大存活时间为30s, 由于计数是毫秒,需要从新计算
    static constexpr uint32_t ARP_ENTRY_TTL_MS = 30 * 1000;

    //! ARP请求报文默认等待(发送间隔)为5s,由于计数是毫秒,需要重新计算
    static constexpr uint32_t ARP_RESPONSE_TTL_MS = 5 * 1000;

源文件代码:

void NetworkInterface::send_datagram(const InternetDatagram &dgram, const Address &next_hop) {
    // convert IP address of next hop to raw 32-bit representation (used in ARP header)
    const uint32_t next_hop_ip = next_hop.ipv4_numeric();
    //查找ARP缓存表中是否有下一跳的IP,如果找到就返回该项的迭代器
    auto arpTable_it = _arp_table.find(next_hop_ip);
    if (arpTable_it != _arp_table.end()) {
        //对于找到IP地址的,直接发送IP数据报
        _send(arpTable_it->second.eth_addr, EthernetHeader::TYPE_IPv4, dgram.serialize());
    } else {
        // 5s内没有对该IP发送国ARP查询报文,则发送
        if (_waiting_arp_response_ip_addr.count(next_hop_ip) == 0) {
            ARPMessage arp_msg;
            //构造请求报文
            arp_msg.opcode = ARPMessage::OPCODE_REQUEST;
            arp_msg.sender_ethernet_address = _ethernet_address;
            arp_msg.target_ethernet_address = {0};
            arp_msg.sender_ip_address = _ip_address.ipv4_numeric();
            arp_msg.target_ip_address = next_hop_ip;
            //以广播模式发送ARP请求报文
            _send(ETHERNET_BROADCAST, EthernetHeader::TYPE_ARP, arp_msg.serialize());
            //需要将此报文加入等待ARP回复的记录表
            _waiting_arp_response_ip_addr[next_hop_ip] = ARP_RESPONSE_TTL_MS;
        }
        //还需要将该未发送的IP报文加入等待发送记录表尾部
        _waiting_internet_datagram[next_hop_ip].emplace_back(next_hop, dgram);
    }
}

//! \param[in] frame the incoming Ethernet frame
optional<InternetDatagram> NetworkInterface::recv_frame(const EthernetFrame &frame) {
    //直接过滤非广播帧和目的MAC地址不是自己的帧
    if (frame.header().dst != ETHERNET_BROADCAST && frame.header().dst != _ethernet_address) {
        return nullopt;
    }
    //如果协议类型是IPv4,则解析成功后返回
    if (frame.header().type == EthernetHeader::TYPE_IPv4) {
        InternetDatagram ret;
        //需要成功解析才返回解析结果,否则返回空
        if (ret.parse(frame.payload()) == ParseResult::NoError) {
            return ret;
        }
        return nullopt;
    }
    //如果协议类型是ARP,则从中查看是否可以获得新的IP->MAC的映射关系
    //可以从ARP请求和响应报文中获得新的arp缓存
    if (frame.header().type == EthernetHeader::TYPE_ARP) {
        ARPMessage arp_msg;
        //解析失败,则返回空
        if (arp_msg.parse(frame.payload()) != ParseResult::NoError) {
            return nullopt;
        }
        const uint32_t self_ip = _ip_address.ipv4_numeric();
        const uint32_t src_ip = arp_msg.sender_ip_address;
        //处理发送给本机的ARP请求报文,需要将自己的MAC地址填入响应报文返回给请求的主机
        if (arp_msg.opcode == ARPMessage::OPCODE_REQUEST && arp_msg.target_ip_address == self_ip) {
            ARPMessage arp_reply;
            //构造返回响应报文
            arp_reply.opcode = ARPMessage::OPCODE_REPLY;
            arp_reply.sender_ethernet_address = _ethernet_address;
            arp_reply.target_ethernet_address = arp_msg.sender_ethernet_address;
            arp_reply.sender_ip_address = self_ip;
            arp_reply.target_ip_address = src_ip;
            _send(arp_msg.sender_ethernet_address, EthernetHeader::TYPE_ARP, arp_reply.serialize());
        }
        //对于接收到的ARP报文都可以获得新的ARP缓存
        _arp_table[src_ip] = {arp_msg.sender_ethernet_address, ARP_ENTRY_TTL_MS};
        //查看等待发送的IP报文记录表中是否符合该IP的
        auto to_send_ip = _waiting_internet_datagram.find(src_ip);
        if (to_send_ip != _waiting_internet_datagram.end()) {
            for (const auto &[next_hop, dgram] : to_send_ip->second) {
                _send(arp_msg.sender_ethernet_address, EthernetHeader::TYPE_IPv4, dgram.serialize());
            }
            _waiting_internet_datagram.erase(to_send_ip);
        }
    }
    return nullopt;
}

//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method
void NetworkInterface::tick(const size_t ms_since_last_tick) {
    //删除ARP缓存表中达到最大存活时间的条目
    for (auto it = _arp_table.begin(); it != _arp_table.end();) {
        if (it->second.ttl <= ms_since_last_tick) {
            it = _arp_table.erase(it);
        } else {
            it->second.ttl -= ms_since_last_tick;
            it = std::next(it);
        }
    }
    //还需要删除等待ARP回复报文列表中超时的项,不做重发处理
    for (auto it = _waiting_arp_response_ip_addr.begin(); it != _waiting_arp_response_ip_addr.end();) {
        if (it->second <= ms_since_last_tick) {
            //没有收到请求MAC地址的IP地址作为记录的回复报文,直接丢弃
            auto it1 = _waiting_internet_datagram.find(it->first);
            if (it1 != _waiting_internet_datagram.end()) {
                _waiting_internet_datagram.erase(it1);
            }
            it = _waiting_arp_response_ip_addr.erase(it);
        } else {
            it->second -= ms_since_last_tick;
            it = std::next(it);
        }
    }
}

//! \param[out] void 无返回值
//! \param[in] det 目的网络接口MAC地址
//! \param[in] type 网络协议类型,用uint16_t类型表示
//! \param[in] payload 载荷,为了BufferList类型右引用
void NetworkInterface::_send(const EthernetAddress &dst, const uint16_t type, BufferList &&payload) {
    EthernetFrame eth_frame;
    eth_frame.header().src = _ethernet_address;
    eth_frame.header().dst = dst;
    eth_frame.header().type = type;
    eth_frame.payload() = std::move(payload);
    _frames_out.push(std::move(eth_frame));
}

然后为了测试,需要修改webget.cc文件中的一行代码:

//将lab4用到的CS144TCPSocket注释掉,换成FullStackSocket
//CS144TCPSocket tcpsock;
FullStackSocket tcpsock;

先是执行arp相关的测试,在terminal中build目录下:

make -j4
ctest -V -R "^arp"

结果如下图:
在这里插入图片描述

然后执行Lab5的测试,同样在terminal中build目录下:

sudo make check_lab5

测试结果如下:
在这里插入图片描述
感觉前面设计不给力,导致后续实验的效率都好低呀········

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值