南京大学本科生实验报告¶
课程名称: 计算机网络 任课教师:田臣
学院 | 工程管理学院 | 专业(方向) | 计算机金融 |
---|---|---|---|
学号 | 211275022 | 姓名 | 田昊东 |
1040880153@qq.com | 开始/完成日期 | 2023/5/13~2023/5/13 |
实验名称¶
Lab 5: Respond to ICMP¶
实验目的¶
- 继实验三、实验四基础上继续完善设计的路由器,补充icmp功能,实现一个较为完善的路由器
实验内容¶
Task 2: Responding to ICMP echo requests¶
设计思路¶
-
将所有的ICMP回复都封装在了一个函数中,即将task2和task3的icmp回复都写在同一函数中,使用type来控制发送ICMP包的类型
-
发送判断:
-
python if ipv4.dst in self.interfaces: if packet.get_header(ICMP) is not None and packet.get_header(ICMP).icmptype == ICMPType.EchoRequest: # icmp答复 packet = icmp_reply(packet, 0)
-
如果一个包的目的ip是路由器的一个端口,并且具有icmp包头且为
EchoRequest
类型,则路由器需要构造一个ICMP答复包,并进入发送程序 -
具体的构造方式及发送逻辑将在task3中介绍
Task 3: Generating ICMP error messages¶
设计思路¶
-
python def icmp_reply(packet, type):#icmp回复包的构建 # 判断是否为表示错误的icmp if packet.has_header(ICMP): icmp = packet.get_header(ICMP) if icmp.icmptype == ICMPType.DestinationUnreachable or icmp.icmptype == ICMPType.TimeExceeded or icmp.icmptype == ICMPType.Redirect: return None # 初始化返回包 reply = Packet() reply_eth = Ethernet() reply_ipv4 = IPv4() reply_icmp = ICMP() reply_ipv4.src = packet[IPv4].src reply_ipv4.dst = packet[IPv4].src reply_ipv4.ttl = 64 reply_ipv4.protocol = IPProtocol.ICMP #去除原始包的以太网包头 del packet[Ethernet] # ping if type == 0: icmp = packet.get_header(ICMP) reply_icmp.icmptype = ICMPType.EchoReply reply_icmp.icmpcode = ICMPCodeEchoReply.EchoReply reply_icmp.icmpdata.identifier = icmp.icmpdata.identifier reply_icmp.icmpdata.sequence = icmp.icmpdata.sequence reply_icmp.icmpdata.data = icmp.icmpdata.data reply_ipv4.src = packet[IPv4].dst # 目的不可达 elif type == 1: reply_icmp.icmptype = ICMPType.DestinationUnreachable reply_icmp.icmpcode = ICMPCodeDestinationUnreachable.NetworkUnreachable reply_icmp.icmpdata.data = packet.to_bytes()[:28] # 超时 elif type == 2: reply_icmp.icmptype = ICMPType.TimeExceeded reply_icmp.icmpcode = ICMPCodeTimeExceeded.TTLExpired reply_icmp.icmpdata.data = packet.to_bytes()[:28] # arp失败 elif type == 3: reply_icmp.icmptype = ICMPType.DestinationUnreachable reply_icmp.icmpcode = ICMPCodeDestinationUnreachable.HostUnreachable reply_icmp.icmpdata.data = packet.to_bytes()[:28] # 路由器无法处理的包 else: reply_icmp.icmptype = ICMPType.DestinationUnreachable reply_icmp.icmpcode = ICMPCodeDestinationUnreachable.PortUnreachable reply_icmp.icmpdata.data = packet.to_bytes()[:28] # 装载 reply.add_header(reply_eth) reply.add_header(reply_ipv4) reply.add_header(reply_icmp) print("发送icmp包", reply, "\n") return reply
-
首先判断传入的需要回复的包是不是一个ICMP错误消息,如果是则直接返回None,告知调用程序不需要进行回复
- 然后进行一些通用初始化,设置ipv4包头的源和目的地址(一致是为了让转发时可以发现这一点,将ipv4发送地址修改为实际发出端口的ip)以及类型(icmp)
- 之后根据type分别初始化,分别表示ping回复以及四种错误类型
- 对于ping回复:首先设置返回包的icmp类型属性,然后将数据从源包的icmp包头复制过来
- 对于错误类型:直接设置
icmptype
与icmpcode
并从源包获取去除以太网包头后的前28字节内容
-
将包头装载到回复包并返回
-
为了方便使用,对原包发送流程进行了打包
-
python def send(self,packet): ipv4 = packet.get_header(IPv4) # 判断目的ip是否在转发表中 target = self.forwarding_table.lookup(ipv4.dst) if (target[0] == None): packet = icmp_reply(packet, 1) if packet is None: return self.send(packet) return # ttl判断 if ipv4.ttl <= 1: packet = icmp_reply(packet, 2) if packet is None: return self.send(packet) return # 计数减一 ipv4.ttl -= 1 # 添加到转发队列 print("等待转发: 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]))
-
首先回答这个问题路由器不应回复任何ICMP错误消息:
-
如果路由器回复ICMP错误信息,那考虑这个场景:首先路由器发现一个包TTL为0了,要发送一个ICMP错误信息包,路由器构造完成后开始准被发送包;而这时又发现包的目的ip在转发表中找不到,路由器就又需要发送一个ICMP包来告知这件事;这也就产生了一个发送给自己的非法包,又会继续产生ICMP错误包,陷入死循环。
-
如果路由器回复ICMP错误信息,那么路由器很可能会陷入为ICMP错误信息发送ICMP错误信息的死循环,因为这很可能意味着一个ICMP错误包根本发不出去。
-
因此路由器应该正常转发ICMP错误信息,而对于出现问题的ICMP错误包应该直接丢弃,不进行回复
-
程序中实现了这一点
-
python if packet is None: return
-
-
send函数包含了两种可能需要回复的情况,一种是转发表中找不到目标ip;另一种是ttl=0
- 打包这个函数是因为发送ICMP包也需要经过转发表映射、arp查询等步骤,使用send函数可以很方便的实现
-
if ipv4.dst in self.interfaces: if packet.get_header(ICMP) is not None and packet.get_header(ICMP).icmptype == ICMPType.EchoRequest: # icmp答复 packet = icmp_reply(packet, 0) else: #错误4 packet = icmp_reply(packet, 4) if packet is None: return self.send(packet)
-
针对目的ip是路由器一个端口的情况,有两种可能,一种是需要回复ping包;另一种是出现了路由器无法处理的包
-
直接调用
icmp_reply
生成icmp包进行处理 -
最后一种可能的错误情况是arp请求失败
-
python while self.router.wait_queue[p.next].qsize(): q = self.router.wait_queue[p.next].get() packet = q.packet packet = icmp_reply(packet, 3) if packet is None: continue self.router.send(packet)
-
当arp请求发送失败5次后,要取出所有对应的待发送的包,逐个移出队列,并针对每个丢弃的包发送一个ICMP包进行反馈
测试结果¶
抓包分析¶
- 使用server1 ping 一个前缀在转发表中但是实际不存在的IP
192.168.200.3
- 首先server1像路由器发送ICMP请求,之后的5s路由器发送了5次arp但都会失败,因此路由器需要把错误信息发还给server1
- 路由器首先发送arp请求,找到server1对应的mac之后将ICMP错误信息发送给server1
- 使用clent ping一个不存在于转发表中的ip,路由器会发现无法转发后将ICMP错误信息返还给client
- 首先使用server1 ping路由器一个端口,路由器正常进行回复,返回一个ping回复包
- 然后发送一个TTL为1的包,路由器将其TTL减一后为0,因此路由器会返回一个ICMP错误包
- tracerouter分析
- 从server1对client发动tracerouter
- 在试探过程中traceroute会逐渐提高ttl的限制(每个ttl会重复发送3个包)
- 在这个过程中路由器先是会返回3次超时包(传输到端口
192.168.100.2
就终止了) - 之后又返回了3次端口不可达包,这是由于UDP包设置错误造成的
- 由此说明路由器可以正确返回ICMP错误包
实验总结¶
-
本次实验的难度相对不是很大,不过想要通过测试案例仍然需要十分细心,处理好协议的每一个细节。
-
实验3~5逐步完成了一个具有基础功能的路由器,在这个过程中尽管遇到了很多挑战,但对层路由器协议有了更为深刻的理解,实际完成过程中更体会到了各种协议设计的精妙!
- 由于实验3~5不断在原有的代码上加以完善,良好代码的结构就显得十分重要,对一些重要功能进行封装、复用可以极大的减少重复代码,并减少错误的可能。