title: go实现tcp/ip协议协议-2 icmp实现ping date: 2022-04-03 17:00:58 tags: tcp categories: tcp

相关协议介绍(后续如无特殊标注都为ipv4)

icmp包分析

如下图,我们可以得出协议的排序结构为,ethernet->ip->icmp,由于我们基于tun实现,没有以太网头,我们只需要接收到ip icmp数据帧,如果使用tap实现(注意需要单独实现arp协议,否则arp表过期后,icmp会ping不通),然后根据icmp数据帧返回对应的数据即可实现icmp协议 img_1.png

   icmp完整数据包如下
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
   | ipheader(20 byte) |icmpheader(8 byte)|icmp data(ip头里定义的长度)  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-

ip头部分析

ip头根据rfc791 定义如下 我们解析出ip头后,可以根据Protocol = 1(icmp)来判断是否为icmp数据帧,如果是,则进行icmp处理,如果不是,则进行下一步 下面我们接着看icmp头部

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version|  IHL  |Type of Service|          Total Length         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Identification        |Flags|      Fragment Offset    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Time to Live |    Protocol   |         Header Checksum       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Source Address                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Destination Address                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Options                    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Protocol Number	Protocol Name	Abbreviation
1	Internet Control Message Protocol	ICMP
6	Transmission Control Protocol	TCP

###icmp头部分析 icmp头根据 rfc791

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Type      |     Code      |          Checksum             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Identifier          |        Sequence Number        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Data ...
+-+-+-+-+-
Type
      8 for echo message;
      0 for echo reply message.

代码部分

首先我们需要读取字节流,解析ip头 这里我们用gopackage有兴趣的可以对应rfc文档看看解析包对应的源码 )包,网络协议部分的包基本封装好了 有兴趣的可以对应rfc文档看看解析包对应的源码
根据解析的ip头里的Protocol字段来判断是否为icmp数据帧

ip4, ok := gopacket.NewPacket(buf, layers.LayerTypeIPv4, gopacket.Default).Layer(layers.LayerTypeIPv4).(*layers.IPv4)
if !ok {
  continue
}
switch ip4.Protocol {
case layers.IPProtocolICMPv4:
	{}

然后将剩余的字节流解析为icmp头部,根据TypeCode判断是否为icmp请求

icmp4, ok := gopacket.NewPacket(ip4.LayerPayload(), layers.LayerTypeICMPv4, gopacket.Default).Layer(layers.LayerTypeICMPv4).(*layers.ICMPv4)
        if !ok {
            continue
        }
        if icmp4.TypeCode != layers.CreateICMPv4TypeCode(layers.ICMPv4TypeEchoRequest, 0) {
            continue
        }

然后构造icmp返回字节流写回我们tun0 具体代码如下

replyIp4 := &layers.IPv4{
        Version:    4,
        IHL:        5,
        TOS:        0,
        Id:         0,
        Flags:      0,
        FragOffset: 0,
        TTL:        255,
        Protocol:   layers.IPProtocolICMPv4,
        SrcIP:      ip4.DstIP,
        DstIP:      ip4.SrcIP,
    }
    replyIcmp4 := &layers.ICMPv4{
        TypeCode: layers.CreateICMPv4TypeCode(layers.ICMPv4TypeEchoReply, 0),
        Id:       icmp4.Id,
        Seq:      icmp4.Seq,
    }
    err = gopacket.SerializeLayers(replyBuf, opt, replyIp4, replyIcmp4, gopacket.Payload(icmp4.Payload))
    if err != nil {
        log.Printf("err:%v", err)
        continue
    }

    _, err = iface.Write(replyBuf.Bytes())

    if err != nil {
        log.Printf("err:%v", err)
        continue
    }

下面我们来测试是否可以正常返回icmp(基于ubuntu20.04)
首先启动程序,然后执行如下

tshark -i tun0 -f icmp
#返回如下
Running as user "root" and group "root". This could be dangerous.
Capturing on 'tun0'
    1 0.000000000  192.168.2.0 → 192.168.2.1  ICMP 84 Echo (ping) request  id=0x00e7, seq=5/1280, ttl=64
    2 0.000121273  192.168.2.1 → 192.168.2.0  ICMP 84 Echo (ping) reply    id=0x00e7, seq=5/1280, ttl=255 (request in 1)
    3 1.023995361  192.168.2.0 → 192.168.2.1  ICMP 84 Echo (ping) request  id=0x00e7, seq=6/1536, ttl=64
    4 1.024119710  192.168.2.1 → 192.168.2.0  ICMP 84 Echo (ping) reply    id=0x00e7, seq=6/1536, ttl=255 (request in 3)
ping 192.168.2.1
#返回如下
PING 192.168.2.1 (192.168.2.1) 56(84) bytes of data.
64 bytes from 192.168.2.1: icmp_seq=1 ttl=255 time=0.157 ms
64 bytes from 192.168.2.1: icmp_seq=2 ttl=255 time=0.196 ms

至此我们icmp已经可以了,后面开始我们的tcp实现