Respond to ICMP | 青训营笔记

116 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的第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 的原始主机:

  1. 构造一个ICMP标头+回显回复。创建EchoReply时:将EchoRequest序列号复制到创建的EchoReply中。将EchoRequest中的标识符复制到EchoReply中。将EchoReply中的数据字段设置为与EchoRequest中的数据相同。
  2. 构造一个IP头。目标IP地址设置为传入ICMP回显请求的源地址,IP源地址设置为路由器的接口地址。数据包中的下一个标头应该是创建的ICMP标头。
  3. 发送构建的数据包

主体函数如下

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错误消息:

  1. 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发回源地址的主机
    
  2. 在转发过程中减少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发回源地址的主机
    
  3. 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])
    
  4. 传入数据包的目的地是分配给路由器接口之一的 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发回源地址的主机
    

    \