V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fansfans
V2EX  ›  程序员

请教:在 Linux 中使用 gopacket 给本机发送 icmp 包无响应

  •  
  •   fansfans · 269 天前 · 700 次点击
    这是一个创建于 269 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在 linux 中使用 gogpacket 给本地发送 icmp 包无响应,windows 中给本机发正常,给其他 Ip 发正常、linux 中给其他 Ip 发包也正常,但是给自己发包五响应,下图前两个包是使用 ping 发送的,显示正常,第三个包是 gopacket 发送的,没有响应

    
    func TestPingPacket(t *testing.T) {
    	dstIP := net.ParseIP("192.168.2.12") // 目标 IP 地址
    	// 创建 ICMPv4 层
    	icmpLayer := &layers.ICMPv4{
    		TypeCode: layers.CreateICMPv4TypeCode(layers.ICMPv4TypeEchoRequest, 0),
    		Id:       uint16(os.Getpid()),
    		Seq:      1,
    	}
    	// 创建 IPv4 层
    	ipLayer := &layers.IPv4{
    		SrcIP:    net.ParseIP("192.168.2.12"), // 源 IP 地址
    		DstIP:    dstIP,
    		Version:  4,
    		TTL:      64,
    		Id:       uint16(os.Getpid()),
    		Protocol: layers.IPProtocolICMPv4,
    		Flags:    layers.IPv4DontFragment,
    	}
    	// 创建以太网( Ethernet )层
    	ethernetLayer := &layers.Ethernet{
    		SrcMAC:       net.HardwareAddr{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // 源 MAC 地址
    		DstMAC:       net.HardwareAddr{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // 目标 MAC 地址
    		EthernetType: layers.EthernetTypeIPv4,                        // 使用 IPv4
    	}
    	h := "cd470e0000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637"
    	b, _ := hex.DecodeString(h)
    	// 构造数据包
    	buffer := gopacket.NewSerializeBuffer()
    
    	// time
    	currentTime := time.Now().Unix()
    	byteData := make([]byte, 8)
    	binary.LittleEndian.PutUint64(byteData, uint64(currentTime))
    	byteData = append(byteData, b...)
    	payload := gopacket.Payload(byteData)
    	serializerOptions := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true}
    	err := payload.SerializeTo(buffer, serializerOptions)
    	if err != nil {
    		println(err.Error())
    		return
    	}
    	err = icmpLayer.SerializeTo(buffer, serializerOptions)
    	if err != nil {
    		println(err.Error())
    		return
    	}
    	err = ipLayer.SerializeTo(buffer, serializerOptions)
    	if err != nil {
    		println(err.Error())
    		return
    	}
    	err = ethernetLayer.SerializeTo(buffer, serializerOptions)
    	if err != nil {
    		println(err.Error())
    		return
    	}
    	handle, err := pcap.OpenLive("lo", 65536, false, pcap.BlockForever)
    	if err != nil {
    		fmt.Println("Error opening interface:", err)
    		return
    	}
    	defer handle.Close()
    	err = handle.WritePacketData(buffer.Bytes())
    	if err != nil {
    		fmt.Println("Error sending packet:", err)
    		return
    	}
    	println("send icmp ok")
    }
    
    
    9 条回复    2023-08-03 18:46:53 +08:00
    zone10
        1
    zone10  
       268 天前
    是想学习 gopacket 还是想实现 icmp 功能, 如果是后者用 go 的标准库几行代码就搞定了, 没必要上 gopacket,
    ```go
    package main

    import "net"

    func main() {
    domain := "www.baidu.com"
    raddr, err := net.ResolveIPAddr("ip", domain)
    if err != nil {
    panic(err)
    }
    conn, err := net.DialIP("ip4:icmp", nil, raddr)
    if err != nil {
    panic(err)
    }
    defer conn.Close()

    buf := []byte{0x08, 0x00, 0xf7, 0xfe, 0x00, 0x01, 0x00, 0x00}
    _, err = conn.Write(buf)
    if err != nil {
    panic(err)
    }
    }
    ```

    如果是前者, 链路层的以太网帧构建出错, 源 MAC 是本机 MAC, 目的 MAC 是网关 MAC, 需要通过 ARP 协议获得, 或者通过 wireshark 抓包硬编码地址也行
    wdf1286
        2
    wdf1286  
       268 天前
    建议先用 tcpdump 抓 lo 看看真实的 ping 包是什么样的再用 gopacket 模仿
    fansfans
        3
    fansfans  
    OP
       268 天前
    @wdf1286 因为 windows 上无法使用 PacketConn 读取数据包,还是的用 gopacket ,因此在 linux 平台也沿用了同一套代码
    192.168.2.12 是本机 Ip,所以 mac 地址是没问题的,这个在抓包对比中确认过了
    fansfans
        4
    fansfans  
    OP
       268 天前
    @zone10 因为 windows 上无法使用 PacketConn 读取数据包,还是的用 gopacket ,因此在 linux 平台也沿用了同一套代码
    192.168.2.12 是本机 Ip,所以 mac 地址是没问题的,这个在抓包对比中确认过了
    fansfans
        5
    fansfans  
    OP
       268 天前
    @wdf1286 对比了两个数据包基本是一致的,并且使用 gopacket 发送有成功过的数据包依旧没有响应,不知道是不是有什么特殊的处理
    fansfans
        6
    fansfans  
    OP
       268 天前
    相同的数据包,使用 packconn 发送有响应,使用 gopacket 无响应
    ![]( https://p.sda1.dev/12/886e2307ff51b283194147cec39d8369/image.png)
    zone10
        7
    zone10  
       268 天前
    @fansfans 好吧, 没看清楚题意, 有两个问题, 第一, 本地主机不走以太网而是用回环地址 Lookback, 不过应该不是包无响应的原因. 如果你把 ping 设置的久一点然后通过抓包就能发现, ping 程序的进程 id 跟 icmp 包头的 id 值是一样的, 所以响应这个 ping 请求的应该就是当前这个 ping 进程自己, 而你的程序没有处理响应 icmp 包的逻辑. 给其他 IP 发正常是因为响应你 icmp 请求的逻辑在其他主机
    zone10
        8
    zone10  
       268 天前
    @zone10 补充一下, windows ping 本机默认走的 loopback 地址, linux 默认走的以太网, linux ping 设置 -A 选项后走的 loopback 地址速度瞬间快了十倍
    fansfans
        9
    fansfans  
    OP
       268 天前
    @zone10 的确如你所说,以下是 chatgpt 的回答:
    在 Linux 系统中,当你使用 `gopacket` 自己构造 ICMP Echo Request 并发送给自己(本地回环地址 127.0.0.1 ),操作系统的内核并不会自动处理这个 ICMP 请求,因为这个请求在内核的 ICMP 协议栈中是不会被处理的。

    当其他主机向你的主机发送 ICMP Echo Request 时,这个请求会经过网络接口,到达内核的网络协议栈。内核会处理 ICMP Echo Request ,然后根据协议处理机制,产生 ICMP Echo Reply 并通过网络接口发送回去。

    但当你自己使用 `gopacket` 构造 ICMP Echo Request 并发送给自己时,数据包并不会经过网络接口,而是直接从应用程序发送到内核中的数据包处理层。在这种情况下,内核的 ICMP 协议栈并不会处理这个 ICMP Echo Request ,因为这个请求并没有经过网络接口。

    因此,当你使用 `gopacket` 构造 ICMP Echo Request 并发送给自己时,你自己的应用程序需要负责处理这个 ICMP Echo Request ,并且根据需要产生 ICMP Echo Reply 并回复给自己。如果你希望收到 ICMP Echo Reply ,你需要在你的程序中对 ICMP Echo Request 进行相应的处理。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3261 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 00:13 · PVG 08:13 · LAX 17:13 · JFK 20:13
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.