这是我参与「第四届青训营 」笔记创作活动的第4天。
本次代码实践中,我们希望完成:
1)Respond to ICMP messages like echo requests ("pings").
2)Generate ICMP error messages when necessary, such as when an IP packet's TTL (time to live) value has been decremented to zero.
Responding to ICMP echo requests
在为传入 IP 数据包做出转发决策之前,检查 IP 目标地址是否与分配给路由器接口的地址之一相同,如果数据包也是 ICMP 回显请求,则应构造一个 ICMP 回显应答,并将其发送回发送 ping 的原始主机:
- 构造一个
ICMP标头+回显回复。创建EchoReply时:将EchoRequest序列号复制到创建的EchoReply中。将EchoRequest中的标识符复制到EchoReply中。将EchoReply中的数据字段设置为与EchoRequest中的数据相同。 - 构造一个
IP头。目标IP地址设置为传入ICMP回显请求的源地址,IP源地址设置为路由器的接口地址。数据包中的下一个标头应该是创建的ICMP标头。 - 发送构建的数据包
主体函数如下
log_info("Receive a packet")
ip_head = packet.get_header(IPv4)
if ip_head is None:
log_info("It's not an IPv4 packet")
else:
log_info("It's an IPv4 packet")
ip_head.ttl -= 1
if ip_head.dst in self.ip_list: # 检查IP目标地址是否与分配给路由器接口的地址之一相同
# 数据包也是ICMP回显请求
if packet.has_header(ICMP) and packet[ICMP].icmptype == ICMPType.EchoRequest:
reply = self.construct_icmp(
packet, ICMPType.EchoReply, 0, self.net.interface_by_name(ifaceName).ipaddr)
# 将EchoRequest序列号复制到创建的EchoReply中
reply[ICMP].icmpdata.sequence = packet[ICMP].icmpdata.sequence
# 将EchoRequest中的标识符复制到EchoReply中
reply[ICMP].icmpdata.identifier = packet[ICMP].icmpdata.identifier
# 将EchoReply中的数据字段设置为与EchoRequest中的数据相同
reply[ICMP].icmpdata.data = packet[ICMP].icmpdata.data
self.forward_packet(reply, queue,ifaceName) # 发送构建的数据包
构建icmp函数如下
def construct_icmp(self, packet, icmptype, icmpcode, src):
ether = Ethernet()
ether.ethertype = EtherType.IP
ippkt = IPv4()
ippkt.src = IPAddr(src)
ippkt.dst = IPAddr(packet[IPv4].src)
ippkt.protocol = IPProtocol.ICMP
ippkt.ttl = 64
ippkt.ipid = 0
icmppkt = ICMP()
icmppkt.icmptype = icmptype
icmppkt.icmpcode = icmpcode
return ether+ippkt+icmppkt
发送构建的数据包函数如下
def forward_packet(self, packet, queue, ifaceName):
flag = 0
prefixlen = -1
for entry in self.forwarding_table:
if(int(packet[IPv4].dst) & int(entry.mask)) == int(entry.prefix):
netaddr = IPv4Network(str(entry.prefix)+"/"+str(entry.mask))
if netaddr.prefixlen > prefixlen:
prefixlen = netaddr.prefixlen
flag = 1
target_entry = entry
if flag == 0: # IP数据包的目标地址与转发表条目不匹配
log_info("ICMP destination network unreachable")
error = self.construct_icmp(
packet, ICMPType.DestinationUnreachable, 0, self.net.interface_by_name(ifaceName).ipaddr)
error[ICMP].icmpdata.origdgramlen = len(packet)
if packet.has_header(Ethernet):
index = packet.get_header_index(Ethernet)
del packet[index]
error[ICMP].icmpdata.data = packet.to_bytes()[:28]
self.forward_packet(error, queue, ifaceName)
# 将ICMP destination network unreachable发回源地址的主机
else:
log_info("Match")
for intf in self.net.interfaces():
if intf.name == target_entry.ifaceName:
target_port = intf
log_info(f"target_port is {target_port}")
if target_entry.nexthop is None:
target_ip = packet[IPv4].dst
else:
if target_entry.nexthop in self.ip_list:
target_ip = None
else:
target_ip = target_entry.nexthop
if target_ip is not None:
if target_ip in self.arp_table.keys():
packet[Ethernet].src = target_port.ethaddr
packet[Ethernet].dst = self.arp_table[target_ip]
self.net.send_packet(target_port, packet)
else:
queue.append(Pac(packet, target_port, target_ip, ifaceName))
Generating ICMP error messages
生成ICMP错误消息:
-
IP数据包的目标地址与转发表条目不匹配:将ICMP destination network unreachable发回源地址的主机if flag == 0: # IP数据包的目标地址与转发表条目不匹配 log_info("ICMP destination network unreachable") error = self.construct_icmp(packet, ICMPType.DestinationUnreachable, 0, self.net.interface_by_name(ifaceName).ipaddr) error[ICMP].icmpdata.origdgramlen = len(packet) if packet.has_header(Ethernet): index = packet.get_header_index(Ethernet) del packet[index] error[ICMP].icmpdata.data = packet.to_bytes()[:28] self.forward_packet(error, queue, ifaceName) # 将ICMP destination network unreachable发回源地址的主机 -
在转发过程中减少
IP数据包的TTL值后,TTL变为零:将ICMP time exceeded发回源地址的主机if ip_head.ttl == 0: # 在转发过程中减少IP数据包的TTL值后,TTL变为零 log_info("ICMP time exceeded") error = self.construct_icmp(packet, ICMPType.TimeExceeded, 0, self.net.interface_by_name(ifaceName).ipaddr) if packet.has_header(Ethernet): index = packet.get_header_index(Ethernet) del packet[index] error[ICMP].icmpdata.data = packet.to_bytes()[:28] error[ICMP].icmpdata.origdgramlen = len(packet) self.forward_packet(error, queue, ifaceName) # 将 ICMP time exceeded发回源地址的主机 -
ARP请求重传5次后路由器没有收到ARP回复:将ICMP destination host unreachable发回源地址的主机if queue[0].count == 5: # ARP请求重传5次后路由器没有收到ARP回复 log_info("ICMP destination host unreachable") error = self.construct_icmp(queue[0].packet, ICMPType.DestinationUnreachable, 1, self.net.interface_by_name(queue[0].ifaceName).ipaddr) if queue[0].packet.has_header(Ethernet): index = queue[0].packet.get_header_index(Ethernet) del queue[0].packet[index] error[ICMP].icmpdata.data = queue[0].packet.to_bytes()[:28] error[ICMP].icmpdata.origdgramlen = len(queue[0].packet) self.forward_packet( error, queue, queue[0].ifaceName) # 将ICMP destination host unreachable发回源地址的主机 del(queue[0]) -
传入数据包的目的地是分配给路由器接口之一的
IP地址,但该数据包不是ICMP回显请求:将ICMP destination port unreachable发回源地址的主机else: # 传入数据包的目的地是分配给路由器接口之一的IP地址,但该数据包不是ICMP回显请求 log_info("ICMP destination port unreachable") error = self.construct_icmp(packet, ICMPType.DestinationUnreachable, 3, self.net.interface_by_name(ifaceName).ipaddr) if packet.has_header(Ethernet): index = packet.get_header_index(Ethernet) del packet[index] error[ICMP].icmpdata.data = packet.to_bytes()[:28] error[ICMP].icmpdata.origdgramlen = len(packet) self.forward_packet(error, queue, ifaceName) # 将ICMP destination port unreachable发回源地址的主机\