南京大学本科生实验报告¶
课程名称: 计算机网络 任课教师:田臣
学院 | 工程管理学院 | 专业(方向) | 计算机金融 |
---|---|---|---|
学号 | 211275022 | 姓名 | 田昊东 |
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 ```
- ```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
-
在本阶段把数据包分成三类来进行处理:
-
目标地址为路由器的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
都是空的则终止线程
- 如果10s内
-
对包的发送主要分为两种: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测试结果
-
testscenario2_advanced.srpy测试结果
抓包分析¶
- 测试过程:
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此后以失败告终
-
eth0
- eth1
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(已废弃):
- 前三位表示8种优先级()越大越高,后后四位表示4种特殊服务类型
-
dscp:
- SCP使用一个6位二进制代码,共有64个不同的值,可以提供不同程度的服务质量,这些值在RFC 2474中定义并分为8个不同的类,每个类中有8个可用的DSCP值。
-
ecn:
- ECN是一种在网络传输中用于通知网络拥塞程度的机制,让网络中的设备能够在不丢失数据的情况下,通知发送方网络拥塞的程度,从而使得发送方能够根据实际的网络拥塞程度进行流量控制。
实验总结¶
- 本次实验难度较大,使用多线程完成更具挑战,总共耗时三天完成,逐步debug到最终通过全部测试真的很艰难,中间还经历了全部推倒重做
- 实验中主要的易错点其实在实验手册faq中大多都提及了,经过这次试验对路由器转发原理、arp协议等有了更深层的理解,虽然艰难但是收获也很多。