V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
zeyexe
V2EX  ›  Go 编程语言

Golang darwin/amd64 平台中解析 IP Packet Header 得到的报文长度错误

  •  
  •   zeyexe · 2021-07-04 22:09:35 +08:00 · 2451 次点击
    这是一个创建于 1272 天前的主题,其中的信息可能已经有所发展或是发生改变。

    直接贴代码,这是从 198.18.255.1 Ping 8.8.8.8(原始 IP 不是这个)返回的 IP 报文:

    package main
    
    import (
    	"encoding/binary"
    	"fmt"
    	"golang.org/x/net/ipv4"
    )
    
    func main() {
    
    	// https://en.wikipedia.org/wiki/IPv4#Total_Length
    	var hdrBytes = []byte{
    		69, 0, // Version |	IHL	| DSCP	| ECN
    		0, 84, // Total_Length
    		125, 47, // Identification
    		0, 0, // Flags | Fragment Offset
    		63,      // Time To Live
    		1,       // Protocol
    		85, 169, // Header Checksum
    		8, 8, 8, 8, // Source IP Address
    		198, 18, 255, 1, //Destination IP Address
    	}
    	header, err := ipv4.ParseHeader(hdrBytes)
    	if err != nil {
    		fmt.Printf("Parse failed. err: %v\n", err)
    	}
    	fmt.Printf("header: %v\n", header)
    	fmt.Printf("header.TotalLen: %d\n", header.TotalLen)
    
    	realTotalLen := binary.BigEndian.Uint16(hdrBytes[2:4])
    	fmt.Printf("realTotalLen: %d\n", realTotalLen)
    
    	totalLenByBigEndian := binary.BigEndian.Uint16(hdrBytes[2:4])
    	fmt.Printf("totalLenByBigEndian: %d\n", totalLenByBigEndian)
    
    	totalLenByLittleEndian := binary.LittleEndian.Uint16(hdrBytes[2:4])
    	fmt.Printf("totalLenByLittleEndian: %d\n", totalLenByLittleEndian)
    
    }
    
    

    这段是解析 IP 报文 Header 的代码,在 Linux 和 Windows 执行得到的 header.TotalLen 都是 84,但是 darwin/amd64 就是 21524 。

    按照相关规范,IP 报文 Header 字段均以大端序包装。这个看起来在 darwin 用了小端序解析。但是就算用小端序,这个值也应该是 21504 。

    看 Go 解析的代码,原来还在解析时加上了 hdrlen 自身的 20 长度,这个似乎有点问题。我不了解那么多关于平台相关的,但是网络 IP 报文应该和平台无关吧。

    
    	switch runtime.GOOS {
    	case "darwin", "ios", "dragonfly", "netbsd":
    		h.TotalLen = int(socket.NativeEndian.Uint16(b[2:4])) + hdrlen # 这里加了 Header 长度
    		h.FragOff = int(socket.NativeEndian.Uint16(b[6:8]))
    	case "freebsd":
    		if freebsdVersion < 1100000 {
    			h.TotalLen = int(socket.NativeEndian.Uint16(b[2:4]))
    			if freebsdVersion < 1000000 {
    				h.TotalLen += hdrlen
    			}
    			h.FragOff = int(socket.NativeEndian.Uint16(b[6:8]))
    		} else {
    			h.TotalLen = int(binary.BigEndian.Uint16(b[2:4]))
    			h.FragOff = int(binary.BigEndian.Uint16(b[6:8]))
    		}
    	default:
    		h.TotalLen = int(binary.BigEndian.Uint16(b[2:4]))
    		h.FragOff = int(binary.BigEndian.Uint16(b[6:8]))
    	}
    
    

    然后 2 年前就有人提出这个问题了,但是还是没人修复。难道没人用 Mac 做 TCP/IP 网络编程吗。

    Ref: https://github.com/golang/go/issues/32118

    13 条回复    2021-07-06 16:49:05 +08:00
    creedowl
        1
    creedowl  
       2021-07-04 22:50:54 +08:00   ❤️ 1
    zeyexe
        2
    zeyexe  
    OP
       2021-07-04 23:07:43 +08:00
    @creedowl 感谢。这解释了计算的方式的问题。但是对 Go 来说还是算错了😓。
    lance6716
        3
    lance6716  
       2021-07-04 23:33:22 +08:00 via Android
    没问题啊,你用 Darwin 当然是收到 Darwin 的包,按照 Darwin 去解析
    zeyexe
        4
    zeyexe  
    OP
       2021-07-05 11:24:45 +08:00
    @lance6716 我是调用了 Golang 的 golang.org/x/net/ipv4 这个库用来解析的,用了它的 `ipv4.ParseHeader.ParseHeader` 解析,事实证明 Golang 没有正确处理。
    lance6716
        5
    lance6716  
       2021-07-05 13:37:01 +08:00 via Android   ❤️ 1
    @zeyexe 如果指的是你样例的话,因为这个数据报不是 Darwin,用 Darwin 当然解析失败
    zeyexe
        6
    zeyexe  
    OP
       2021-07-05 15:21:08 +08:00   ❤️ 1
    @lance6716 这个数据是从 Mac 的网卡里面读取来交给 Go 处理的。
    zhaiblog
        7
    zhaiblog  
       2021-07-05 17:35:44 +08:00   ❤️ 1
    https://play.golang.org/p/CPoWMXNC5DI
    拿到本地去跑一下,试了试是正常的。

    ```bash
    IP header: 45 00 1E 00 00 00 00 00 40 01 00 00 00 00 00 00 27 9C 45 4F
    Ping reply 45 00 0A 00 C8 5A 00 00 31 01 74 D1 27 9C 45 4F C0 A8 1F 20 00 00 3F 21 00 00 00 00 C0 DE
    header: ver=4 hdrlen=20 tos=0x0 totallen=30 id=0xc85a flags=0x0 fragoff=0x0 ttl=49 proto=1 cksum=0x74d1 src=39.156.69.79 dst=192.168.31.32
    header.TotalLen: 30
    realTotalLen: 2560
    totalLenByBigEndian: 2560
    totalLenByLittleEndian: 10
    ```

    也就是说,从 MAC 的网卡上读取到的 TotalLen 数据,其实是小端序的。
    zhaiblog
        8
    zhaiblog  
       2021-07-05 17:38:27 +08:00
    @zeyexe lance6716 说的是,你用来测试的数据不是从 Darwin 的网卡上读取出来的,如果你是 Wireshark 上抓包的话,Wireshark 对 IP 报已经处理过了。
    你可以试试我发的那个 Go Playground,它是用 f.Read(buf) 直接从网卡上读取数据的。
    zhaiblog
        9
    zhaiblog  
       2021-07-05 17:58:04 +08:00
    也就是说,如果是你想在 Mac 下解析从别的平台( Linux,Windows )下抓下来的包,那你得手动复制一个 golang 的 parse 函数了。
    zeyexe
        10
    zeyexe  
    OP
       2021-07-05 18:52:26 +08:00
    @zhaiblog 我的场景是从 tun 设备读取三层 IP 数据包,然后发送到服务端。然后我从 tun 读取到三层 IP 数据包是大端序的,这时候是 request 包,src 本机,dst 对端机,用 ipv4.ParseHeader 就会得到错误的长度。用你的代码从网卡读数据,这时候读取到的是 reply 包,src 对端机,dst 本机,得到的是小端序的,这时候用 ipv4.ParseHeader 就正常了。

    看起来 Mac 网络协议栈发送出去的包还是大端序网络包。但是给到本机应用程序的是小端序网络包。
    zhaiblog
        11
    zhaiblog  
       2021-07-05 21:49:57 +08:00
    @zeyexe 这样,那它这个从链路层到网络层的转换的"feature",有点太多此一举了。
    ninerec
        12
    ninerec  
       2021-07-06 14:54:05 +08:00   ❤️ 1
    没有细看问题。但 golang 的 IP 处理确实是多被吐槽。或许可以看看这篇文章: https://tailscale.com/blog/netaddr-new-ip-type-for-go/
    可以试试用 https://github.com/inetaf/netaddr
    zeyexe
        13
    zeyexe  
    OP
       2021-07-06 16:49:05 +08:00
    @ninerec 感谢推荐。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2579 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 04:17 · PVG 12:17 · LAX 20:17 · JFK 23:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.