底层网络编程

虽然 http.Transport 结构允许您修改网络连接的底层参数,但您可以编写允许您读取网络包原始数据的 Go 代码。这有两个棘手的问题:

  • 网络包采用二进制格式,这要求您查找特定类型的网络数据包,而不仅仅是任何类型的网络数据包。
  • 为了发送一个网络数据包,您必须自己构建。

接下来要展示的是 lowLevel.go,并把它分为三个部分。注意 lowLevel.go 捕获 ICMP 数据包,使用 IPv4 协议并打印包的内容。另外,由于安全原因操作原始网络数据需求 root 权限。

lowLevel.go 的第一段如下:

  1. package main
  2. import(
  3. "fmt"
  4. "net"
  5. )

lowLevel.go 的第二段代码如下:

  1. func main() {
  2. netaddr, err := net.ResolveIPAddr("ip4", "127.0.0.1")
  3. if err != nil {
  4. fmt.Println(err)
  5. return
  6. }
  7. conn, err := net.ListenIP("ip4:icmp", netaddr)
  8. if err != nil {
  9. fmt.Println(err)
  10. return
  11. }

ICMP 协协议被定义在 net.ListenIP() 函数的第一个参数的第二部分。此外,ip4 部分告诉程序只捕获 IPv4 流量。

lowLevel.go 的其余 Go 代码如下:

  1. buffer := make([]byte, 1024)
  2. n, _, err := conn.ReadFrom(buffer)
  3. if err != nil {
  4. fmt.Println(err)
  5. return
  6. }
  7. fmt.Printf("% X\n", buffer[0:n])
  8. }

上面这段代码告诉 lowLevel.go 只读取一个网络包,因为没有 for 循环。

ICMP 协议由 ping(1)traceroute(1) 命令使用,所以为了产生 ICMP 流量,可以使用它们中的任何一个。当 lowLevel.go 已经运行后,在所有的 Unix 机器上使用如下命令就会产生 ICMP 网络流量。

  1. $ ping -c 5 localhost
  2. PING localhost (127.0.0.1): 56 data bytes
  3. 64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.037 ms
  4. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.038 ms
  5. 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.117 ms
  6. 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.052 ms
  7. 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.049 ms
  8. --- localhost ping statistics ---
  9. 5 packets transmitted, 5 packets received, 0.0% packet loss
  10. round-trip min/avg/max/stddev = 0.037/0.059/0.117/0.030 ms
  11. $ traceroute localhost
  12. traceroute to localhost (127.0.0.1), 64 hops max, 52 byte packets
  13. 1 localhost(127.0.0.1) 0.255 ms 0.048 ms 0.067 ms

在 macOS High Sierra 机器上用 root 权限执行 lowLevel.go 将产生如下输出:

  1. $ sudo go run lowlevel.go
  2. 03 03 CD DA 00 00 00 00 45 00 34 00 B4 0F 00 00 01 00 00 7F 00 00 01 7F 00 00 01 B4 0E 82 9B 00 20 00 00
  3. $ sudo go run lowLevel.go
  4. 00 00 0B 3B 20 34 00 00 5A CB 5C 15 00 04 32 A9 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37

第一个输出的例子是 ping(1) 命令产生的,第二个是 traceroute(1) 命令产生的。

在 Debian Linux 机器上运行 lowLevel.go 将产生如下输出:

  1. $ uname -a
  2. Linux mail 4.14.12-x86_64-linode92 #1 SMP Fri Jan 5 15:34:44 UTC 2018 x86_64 GNU/Linux
  3. # go run lowLevel.go
  4. 08 00 61 DD 3F BA 00 01 9A 5D CB 5A 00 00 00 00 26 DC 0B 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37
  5. # go run lowLevel.go
  6. 03 03 BB B8 00 00 00 00 45 00 00 3C CD 8D 00 00 01 11 EE 21 7F 00 00 01 7F 00 00 01 CB 40 82 9A 00 28 FE 3B 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F

uname(1) 命令打印出 Linux 系统的有用信息。注意,在当前的 Linux 机器上,您应该在执行 ping(1) 命令时使用 -4 标志来告诉它使用 IPv4 协议。