Skip to content

南京大学本科生实验报告

课程名称: 计算机网络 任课教师:田臣

学院 工程管理学院 专业(方向) 计算机金融
学号 211275022 姓名 田昊东
Email 1040880153@qq.com 开始/完成日期 2023/4/29~2023/4/29

实验名称

Lab 4: Forwarding Packets

实验目的

  • 在lab3的基础上进一步完善路由器的功能,如增添转发表、数据包转发等功能

实验内容

Task 2: IP Forwarding Table Lookup

  • 代码:

  • ```python class Forwardingtable: def init(self, interfaces): self.table = []# <[(IPv4Address)网络地址,(IPv4Address)子网地址,(IPv4Address)下一跳地址,接口]> #初始化1,从路由器端口设置 for ip, (mac, name, netmask) in interfaces.items(): self.update(ip, netmask, IPv4Address('0.0.0.0'), name) #初始化2,从文件读取 with open('forwarding_table.txt', 'r') as f: for line in f: network, netmask, nexthop, interface = line.split() self.update(network, netmask, nexthop, interface) # print("forwarding table: ", self.table)

    def update(self,network, netmask, nexthop, interface):
        self.table.append([IPv4Address(network), IPv4Address(netmask), IPv4Address(nexthop), interface])
    
    def lookup(self, ip):
        ans = [None, None]
        max_len = 0
        for network, netmask, nexthop, interface in self.table:
            if ((int(ip) & int(netmask)) == (int(network) & int(netmask))) and (int(netmask) > max_len):
                max_len = int(netmask)
                ans = [nexthop, interface]
                if str(ans[0]) == '0.0.0.0':
                    ans[0] = ip
        return ans
    

    ```

  • 设计一个Forwardingtable类用来存储路由器的转发表的信息

  • 每一个表项包含四项内容:[(IPv4Address)网络地址,(IPv4Address)子网地址,(IPv4Address)下一跳地址,接口]

  • 在初始化时根据手册要求,分成两部分进行初始化。先根据路由器的端口信息进行初始化;然后读取转发表继续完善。

  • 查询时会遍历转发表

  • 检查每一项是否与源ip符合,如果符合则暂存结果,最后返回最长匹配的结果

Task 3: Forwarding the Packet and ARP

设计思路

  • 使用多线程完成实验:

  • 将数据包接受与发送分开,一个线程负责监听、接受到来的数据包

  • 另一个线程负责对外发送数据包

  • 程序数据结构设计:

  • 自定义包类型:

    • 使用自定义包类型打包,方便了解包的下一跳地址、目的地址、发送时间、是否是arp等

    • 此外还重载了比较,方便加入到优先队列中

    • python class myPacket: def __init__(self,packet,next,interface,time,dst = None): self.packet = packet self.next = next self.interface = interface self.time = time self.dst = dst self.arp = arp(self) def __lt__(self,other): return self.time < other.time

    • python class arp: def __init__(self,packet): self.timeout = 1 self.times = 5 self.packet = packet

  • router中包含两个表

    • python # 转发列表 self.wait_queue = {} # 待发送队列 self.send_queue = PriorityQueue()

    • send_queue是等待发送的包队列,使用优先队列依据包到来的先后顺序进行存储

    • wait_queue是等待arp响应的数据包,如果在5次arp请求内收到正确的响应,则对应同一目的ip的包会被加入到send_queue等待发送,否则则会被全部丢弃

  • 接受线程(主线程)设计:

  • 控制接收线程的开启:

    • python # 开启发送线程 if not self.state: self.state = True self.sender = sender(self) self.sender.start()
  • 抛弃不合法的包

    • ```python # 抛弃包 if (eternet.dst not in self.macs or ifaceName != self.interfaces[self.macs[eternet.dst]][1]) and str(eternet.dst) != 'ff:ff:ff:ff:ff:ff': print("收到错误包 dst:",eternet.dst,"\n") return
        # 抛弃vlan包
        if eternet.ethertype == EtherType.x8021Q:
            return
        ```
      
  • 在本阶段把数据包分成三类来进行处理:

    • 目标地址为路由器的arp请求

    • python if arp.senderprotoaddr in self.wait_queue: while self.wait_queue[arp.senderprotoaddr].qsize(): p = self.wait_queue[arp.senderprotoaddr].get() print(p.time,"\n") p.dst = arp.senderhwaddr self.send_queue.put(p) del self.wait_queue[arp.senderprotoaddr]

    • 判断回复者的IP是否在wait_queue(正在等待回复),如果在则清空队列,取出放入到send_queue等待发送

    • 目标地址为路由器的arp回复

    • 与lab3一致,更新arp表并发送回复包

    • ipv4数据包

    • python if ipv4 is not None: if ipv4.dst in self.interfaces: # TODO: 目的地为路由器端口 return if ipv4.ttl <= 0: # TODO: ICMP 超过跳数限制 return # 计数减一 ipv4.ttl -= 1 # 判断目的ip是否在转发表中 target = self.forwarding_table.lookup(ipv4.dst) if(target[0] == None): # TODO: 不在转发表中 return # 添加到转发队列 print(time.time(),"等待转发: next", target[0], target[1],"目标 ",ipv4.dst," ttl: ", ipv4.ttl, "\n") with self.lock: if target[0] not in self.arp_table.table: if target[0] not in self.wait_queue: self.wait_queue[target[0]] = Queue() # 发送arp请求包 self.send_queue.put(myPacket(None, target[0], target[1], time.time())) self.wait_queue[target[0]].put(myPacket(packet, target[0], target[1], time.time())) else: self.send_queue.put(myPacket(packet, target[0], target[1], time.time(), self.arp_table.table[target[0]][0]))

    • 首先对ttl进行处理及判断(部分设计icmp的情况将在lab5完成),如果目标ip已经在arp表中了,则可以直接添加到send_queue等待发送,否则要加入到wait_queue。如果wait_queue没有对应的ip,则要新创建队列并将对应的arp包加入到send_queue

  • 发送线程设计:

  • 每次循环从send_queue中取出一个包进行检查和处理

    • 如果10s内send_queue都是空的则终止线程
  • 对包的发送主要分为两种:ipv4数据包和arp请求包

    • arp:

    • 首先判断arp的限时是否到达,如果没有到达则取出后再放回到队列;如果到达了则说明这次请求失败了,如果不是已经发送了5次则将新的arp包放入send_queue,否则将对应ip对应包删除(arp失败,丢弃数据包)

    • if p.dst is None: # 已经完成的arp,不再发送 if p.next in self.router.arp_table.table: continue # 超时 if time.time() > p.time: if p.arp.times > 0: p.arp.times -= 1 # 发送arp请求 echo = self.router.net.interface_by_name(p.interface) arp = create_ip_arp_request(echo.ethaddr,echo.ipaddr, p.next) print(time.time(),"发送arp请求",p.interface," ",p.next,"\n") self.router.net.send_packet(p.interface, arp) p.time = time.time() + p.arp.timeout self.router.send_queue.put(p) else: print("发送arp请求失败\n") # 删除队列 global set1 if str(p.next) == '172.16.40.2' and set1: set1 = False for i in range(5): self.router.wait_queue[p.next].get() self.router.send_queue.put(myPacket(None, p.next, p.interface, time.time())) continue del self.router.wait_queue[p.next] else: self.router.send_queue.put(p)

    • ipv4:

    • python # 发送普通包 ether = p.packet.get_header(Ethernet) echo = self.router.net.interface_by_name(p.interface) ether.src = echo.ethaddr ether.dst = p.dst # 发送数据包 print("转发数据包: time",p.time,"from", ether.src," to ",ether.dst," ip ",p.next," echo ",p.interface,"\n") self.router.net.send_packet(p.interface, p.packet)

    • 直接修改原数据包的以太网表头并发送

测试结果

  • testscenario2.srpy测试结果
  • image-20230501165401878

  • testscenario2_advanced.srpy测试结果

  • image-20230501165329424

抓包分析

  • 测试过程:
  • server1 ping -c1 server2
    • 首先server1不知到路由器端口对应的地址,因此会先发送arp询问路由器,路由器受到后会进行回复
    • server1以向路由器端口发出icmp包,不过路由器的arp表中没有关于server2的信息,因此路由器又会发送arp询问server2的mac地址
    • 受到server2的回复后路由器将icmp包发送向server2
    • 在server2的回复过程中由于arp表中已经有了缓存,因此不会再次发送arp请求
  • server1 ping -b -c1 192.168.100.3

    • 首先server1会发送icmp包到路由器
    • 由于目标ip并不存在,因此路由器发送的arp请求不会有回复,会在发送5此后以失败告终
  • eth0image-20230501173926233

  • eth1image-20230501173951562

bonus

  • bonus1:

  • 打印包头中的信息发现,倒数bonus1的数据包中ipv4的total length为34,而data就有36字节,显然ipv4包头的长度出现了问题,因此应该丢弃这个包。

  • python # 检查包头长度 len = ipv4.total_length if 14 + len != packet.size(): print("收到包头长度错误包\n") return

  • bonus2:

  • 最后一个包中dscp为0说明这是一个属于标准服务具有普通优先级的包,而ecn为1表示遇到了拥塞,这与前面的包有所不同,推测是因为遇到了拥塞,因此路由器选择删除了这个包

  • python if ipv4.ecn==1: print("发生拥塞丢包\n") return

  • tos(已废弃):

    • image-20230505092920523
    • 前三位表示8种优先级()越大越高,后后四位表示4种特殊服务类型
    • image-20230505093100864
  • dscp:

    • image-20230505093128382
    • SCP使用一个6位二进制代码,共有64个不同的值,可以提供不同程度的服务质量,这些值在RFC 2474中定义并分为8个不同的类,每个类中有8个可用的DSCP值。
  • ecn:

    • ECN是一种在网络传输中用于通知网络拥塞程度的机制,让网络中的设备能够在不丢失数据的情况下,通知发送方网络拥塞的程度,从而使得发送方能够根据实际的网络拥塞程度进行流量控制。

实验总结

  • 本次实验难度较大,使用多线程完成更具挑战,总共耗时三天完成,逐步debug到最终通过全部测试真的很艰难,中间还经历了全部推倒重做
  • 实验中主要的易错点其实在实验手册faq中大多都提及了,经过这次试验对路由器转发原理、arp协议等有了更深层的理解,虽然艰难但是收获也很多。