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

2021-07-04 22:09:35 +08:00
 zeyexe

直接贴代码,这是从 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

2444 次点击
所在节点    Go 编程语言
13 条回复
creedowl
2021-07-04 22:50:54 +08:00
zeyexe
2021-07-04 23:07:43 +08:00
@creedowl 感谢。这解释了计算的方式的问题。但是对 Go 来说还是算错了😓。
lance6716
2021-07-04 23:33:22 +08:00
没问题啊,你用 Darwin 当然是收到 Darwin 的包,按照 Darwin 去解析
zeyexe
2021-07-05 11:24:45 +08:00
@lance6716 我是调用了 Golang 的 golang.org/x/net/ipv4 这个库用来解析的,用了它的 `ipv4.ParseHeader.ParseHeader` 解析,事实证明 Golang 没有正确处理。
lance6716
2021-07-05 13:37:01 +08:00
@zeyexe 如果指的是你样例的话,因为这个数据报不是 Darwin,用 Darwin 当然解析失败
zeyexe
2021-07-05 15:21:08 +08:00
@lance6716 这个数据是从 Mac 的网卡里面读取来交给 Go 处理的。
zhaiblog
2021-07-05 17:35:44 +08:00
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
2021-07-05 17:38:27 +08:00
@zeyexe lance6716 说的是,你用来测试的数据不是从 Darwin 的网卡上读取出来的,如果你是 Wireshark 上抓包的话,Wireshark 对 IP 报已经处理过了。
你可以试试我发的那个 Go Playground,它是用 f.Read(buf) 直接从网卡上读取数据的。
zhaiblog
2021-07-05 17:58:04 +08:00
也就是说,如果是你想在 Mac 下解析从别的平台( Linux,Windows )下抓下来的包,那你得手动复制一个 golang 的 parse 函数了。
zeyexe
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
2021-07-05 21:49:57 +08:00
@zeyexe 这样,那它这个从链路层到网络层的转换的"feature",有点太多此一举了。
ninerec
2021-07-06 14:54:05 +08:00
没有细看问题。但 golang 的 IP 处理确实是多被吐槽。或许可以看看这篇文章: https://tailscale.com/blog/netaddr-new-ip-type-for-go/
可以试试用 https://github.com/inetaf/netaddr
zeyexe
2021-07-06 16:49:05 +08:00
@ninerec 感谢推荐。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/787485

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX