直接贴代码,这是从 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 网络编程吗。
1
creedowl 2021-07-04 22:50:54 +08:00 1
|
3
lance6716 2021-07-04 23:33:22 +08:00 via Android
没问题啊,你用 Darwin 当然是收到 Darwin 的包,按照 Darwin 去解析
|
4
zeyexe OP @lance6716 我是调用了 Golang 的 golang.org/x/net/ipv4 这个库用来解析的,用了它的 `ipv4.ParseHeader.ParseHeader` 解析,事实证明 Golang 没有正确处理。
|
5
lance6716 2021-07-05 13:37:01 +08:00 via Android 1
@zeyexe 如果指的是你样例的话,因为这个数据报不是 Darwin,用 Darwin 当然解析失败
|
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 数据,其实是小端序的。 |
8
zhaiblog 2021-07-05 17:38:27 +08:00
@zeyexe lance6716 说的是,你用来测试的数据不是从 Darwin 的网卡上读取出来的,如果你是 Wireshark 上抓包的话,Wireshark 对 IP 报已经处理过了。
你可以试试我发的那个 Go Playground,它是用 f.Read(buf) 直接从网卡上读取数据的。 |
9
zhaiblog 2021-07-05 17:58:04 +08:00
也就是说,如果是你想在 Mac 下解析从别的平台( Linux,Windows )下抓下来的包,那你得手动复制一个 golang 的 parse 函数了。
|
10
zeyexe OP @zhaiblog 我的场景是从 tun 设备读取三层 IP 数据包,然后发送到服务端。然后我从 tun 读取到三层 IP 数据包是大端序的,这时候是 request 包,src 本机,dst 对端机,用 ipv4.ParseHeader 就会得到错误的长度。用你的代码从网卡读数据,这时候读取到的是 reply 包,src 对端机,dst 本机,得到的是小端序的,这时候用 ipv4.ParseHeader 就正常了。
看起来 Mac 网络协议栈发送出去的包还是大端序网络包。但是给到本机应用程序的是小端序网络包。 |
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 |