原始套接字未收到 ICMP 响应。

huangapple go评论129阅读模式
英文:

raw socket didn't receive icmp response

问题

我正在尝试发送一个TTL为1的ICMP消息,并期望接收到一个超时消息。我可以从Wireshark中看到该消息的到达,但我的程序在syscall.Recvfrom上阻塞。有人知道为什么吗?

以下是你提供的代码的翻译:

  1. package main
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "fmt"
  6. "net"
  7. "os"
  8. "syscall"
  9. )
  10. type ICMP struct {
  11. Type uint8
  12. Code uint8
  13. Checksum uint16
  14. Identifier uint16
  15. SeqNo uint16
  16. }
  17. func Checksum(data []byte) uint16 {
  18. var (
  19. sum uint32
  20. length int = len(data)
  21. index int
  22. )
  23. for length > 1 {
  24. sum += uint32(data[index])<<8 + uint32(data[index+1])
  25. index += 2
  26. length -= 2
  27. }
  28. if length > 0 {
  29. sum += uint32(data[index])
  30. }
  31. sum += (sum >> 16)
  32. return uint16(^sum)
  33. }
  34. func main() {
  35. h := Header{
  36. Version: 4,
  37. Len: 20,
  38. TotalLen: 20 + 8,
  39. TTL: 1,
  40. Protocol: 1,
  41. // Dst:
  42. }
  43. argc := len(os.Args)
  44. if argc < 2 {
  45. fmt.Println("usage: program + host")
  46. return
  47. }
  48. ipAddr, _ := net.ResolveIPAddr("ip", os.Args[1])
  49. h.Dst = ipAddr.IP
  50. icmpReq := ICMP{
  51. Type: 8,
  52. Code: 0,
  53. Identifier: 0,
  54. SeqNo: 0,
  55. }
  56. out, err := h.Marshal()
  57. if err != nil {
  58. fmt.Println("ip header error", err)
  59. return
  60. }
  61. var icmpBuf bytes.Buffer
  62. binary.Write(&icmpBuf, binary.BigEndian, icmpReq)
  63. icmpReq.Checksum = Checksum(icmpBuf.Bytes())
  64. icmpBuf.Reset()
  65. binary.Write(&icmpBuf, binary.BigEndian, icmpReq)
  66. fd, _ := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
  67. addr := syscall.SockaddrInet4{
  68. Port: 0,
  69. }
  70. copy(addr.Addr[:], ipAddr.IP[12:16])
  71. pkg := append(out, icmpBuf.Bytes()...)
  72. fmt.Println("ip length", len(pkg))
  73. if err := syscall.Sendto(fd, pkg, 0, &addr); err != nil {
  74. fmt.Println("Sendto err:", err)
  75. }
  76. var recvBuf []byte
  77. if nBytes, rAddr, err := syscall.Recvfrom(fd, recvBuf, 0); err == nil {
  78. fmt.Printf("recv %d bytes from %v\n", nBytes, rAddr)
  79. }
  80. }

另外,我使用了来自https://github.com/golang/net/tree/master/ipv4的header.gohelper.go文件。

英文:

I'm trying to send an icmp message whose TTL is just 1, and expect to receive a time exceeded message. that message does come(I see it from wireshark), but my program blocks on syscall.Recvfrom. Anyone knows why?
icmp.go

  1. package main
  2. import (
  3. &quot;bytes&quot;
  4. &quot;encoding/binary&quot;
  5. &quot;fmt&quot;
  6. &quot;net&quot;
  7. &quot;os&quot;
  8. &quot;syscall&quot;
  9. )
  10. type ICMP struct {
  11. Type uint8
  12. Code uint8
  13. Checksum uint16
  14. Identifier uint16
  15. SeqNo uint16
  16. }
  17. func Checksum(data []byte) uint16 {
  18. var (
  19. sum uint32
  20. length int = len(data)
  21. index int
  22. )
  23. for length &gt; 1 {
  24. sum += uint32(data[index])&lt;&lt;8 + uint32(data[index+1])
  25. index += 2
  26. length -= 2
  27. }
  28. if length &gt; 0 {
  29. sum += uint32(data[index])
  30. }
  31. sum += (sum &gt;&gt; 16)
  32. return uint16(^sum)
  33. }
  34. func main() {
  35. h := Header{
  36. Version: 4,
  37. Len: 20,
  38. TotalLen: 20 + 8,
  39. TTL: 1,
  40. Protocol: 1,
  41. // Dst:
  42. }
  43. argc := len(os.Args)
  44. if argc &lt; 2 {
  45. fmt.Println(&quot;usage: program + host&quot;)
  46. return
  47. }
  48. ipAddr, _ := net.ResolveIPAddr(&quot;ip&quot;, os.Args[1])
  49. h.Dst = ipAddr.IP
  50. icmpReq := ICMP{
  51. Type: 8,
  52. Code: 0,
  53. Identifier: 0,
  54. SeqNo: 0,
  55. }
  56. out, err := h.Marshal()
  57. if err != nil {
  58. fmt.Println(&quot;ip header error&quot;, err)
  59. return
  60. }
  61. var icmpBuf bytes.Buffer
  62. binary.Write(&amp;icmpBuf, binary.BigEndian, icmpReq)
  63. icmpReq.Checksum = Checksum(icmpBuf.Bytes())
  64. icmpBuf.Reset()
  65. binary.Write(&amp;icmpBuf, binary.BigEndian, icmpReq)
  66. fd, _ := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
  67. addr := syscall.SockaddrInet4{
  68. Port: 0,
  69. }
  70. copy(addr.Addr[:], ipAddr.IP[12:16])
  71. pkg := append(out, icmpBuf.Bytes()...)
  72. fmt.Println(&quot;ip length&quot;, len(pkg))
  73. if err := syscall.Sendto(fd, pkg, 0, &amp;addr); err != nil {
  74. fmt.Println(&quot;Sendto err:&quot;, err)
  75. }
  76. var recvBuf []byte
  77. if nBytes, rAddr, err := syscall.Recvfrom(fd, recvBuf, 0); err == nil {
  78. fmt.Printf(&quot;recv %d bytes from %v\n&quot;, nBytes, rAddr)
  79. }
  80. }

additionally, I use header.go and helper.go from https://github.com/golang/net/tree/master/ipv4

答案1

得分: 2

正如Andy指出的,raw(7) man page中提到:

> IPPROTO_RAW套接字只能用于发送。如果你真的想接收所有的IP数据包,请使用具有ETH_P_IP协议的packet(7)套接字。请注意,与原始套接字不同,数据包套接字不会重新组装IP片段。

我知道如果我在创建套接字时将协议设置为IPPROTO_ICMP,我可以接收到ICMP回复,但是我需要在IP层设置TTL为1。因此,我使用IPPROTO_RAW套接字发送ICMP请求,然后使用net.ListenIP接收ICMP消息。以下是代码:

  1. package main
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "log"
  6. "net"
  7. "os"
  8. "syscall"
  9. )
  10. const icmpID uint16 = 43565 // 暂时使用一个魔术数
  11. type ICMP struct {
  12. Type uint8
  13. Code uint8
  14. Checksum uint16
  15. Identifier uint16
  16. SeqNo uint16
  17. }
  18. func Checksum(data []byte) uint16 {
  19. var (
  20. sum uint32
  21. length int = len(data)
  22. index int
  23. )
  24. for length > 1 {
  25. sum += uint32(data[index])<<8 + uint32(data[index+1])
  26. index += 2
  27. length -= 2
  28. }
  29. if length > 0 {
  30. sum += uint32(data[index])
  31. }
  32. sum += (sum >> 16)
  33. return uint16(^sum)
  34. }
  35. func main() {
  36. h := Header{
  37. Version: 4,
  38. Len: 20,
  39. TotalLen: 20 + 8,
  40. TTL: 1,
  41. Protocol: 1,
  42. }
  43. argc := len(os.Args)
  44. if argc < 2 {
  45. log.Println("usage: program + host")
  46. return
  47. }
  48. ipAddr, _ := net.ResolveIPAddr("ip", os.Args[1])
  49. h.Dst = ipAddr.IP
  50. icmpReq := ICMP{
  51. Type: 8,
  52. Code: 0,
  53. Identifier: icmpID,
  54. SeqNo: 1,
  55. }
  56. out, err := h.Marshal()
  57. if err != nil {
  58. log.Println("ip header error", err)
  59. return
  60. }
  61. var icmpBuf bytes.Buffer
  62. binary.Write(&icmpBuf, binary.BigEndian, icmpReq)
  63. icmpReq.Checksum = Checksum(icmpBuf.Bytes())
  64. icmpBuf.Reset()
  65. binary.Write(&icmpBuf, binary.BigEndian, icmpReq)
  66. fd, _ := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
  67. addr := syscall.SockaddrInet4{
  68. Port: 0,
  69. }
  70. copy(addr.Addr[:], ipAddr.IP[12:16])
  71. pkg := append(out, icmpBuf.Bytes()...)
  72. if err := syscall.Sendto(fd, pkg, 0, &addr); err != nil {
  73. log.Println("Sendto err:", err)
  74. }
  75. laddr, err := net.ResolveIPAddr("ip4:icmp", "0.0.0.0")
  76. if err != nil {
  77. log.Fatal(err)
  78. }
  79. c, err := net.ListenIP("ip4:icmp", laddr)
  80. if err != nil {
  81. log.Fatal(err)
  82. }
  83. for {
  84. buf := make([]byte, 2048)
  85. n, raddr, err := c.ReadFrom(buf)
  86. if err != nil {
  87. log.Println(err)
  88. continue
  89. }
  90. icmpType := buf[0]
  91. if icmpType == 11 {
  92. if n == 36 { // Time exceeded messages
  93. // A time exceeded message contain IP header(20 bytes) and first 64 bits of the original payload
  94. id := binary.BigEndian.Uint16(buf[32:34])
  95. log.Println("recv id", id)
  96. if id == icmpID {
  97. log.Println("recv Time Exceeded from", raddr)
  98. }
  99. }
  100. }
  101. }
  102. }

实际上,我正在使用Go语言编写一个traceroute程序,如果有人对此感兴趣,完整的代码可以在GitHub上找到。

英文:

As Andy pointed out, the raw(7) man page says:

> An IPPROTO_RAW socket is send only. If you really want to receive
all IP packets, use a packet(7) socket with the ETH_P_IP protocol.
Note that packet sockets don't reassemble IP fragments, unlike raw
sockets.

I know I can receive ICMP reply if I set IPPROTO_ICMP as the protocol when I create the socket, but I need to set TTL to 1 which must be done in IP layer. Therefore I send the ICMP request with IPPROTO_RAW socket, after that I use net.ListenIP to receive ICMP messages. Here is the code:

  1. package main
  2. import (
  3. &quot;bytes&quot;
  4. &quot;encoding/binary&quot;
  5. &quot;log&quot;
  6. &quot;net&quot;
  7. &quot;os&quot;
  8. &quot;syscall&quot;
  9. )
  10. const icmpID uint16 = 43565 // use a magic number for now
  11. type ICMP struct {
  12. Type uint8
  13. Code uint8
  14. Checksum uint16
  15. Identifier uint16
  16. SeqNo uint16
  17. }
  18. func Checksum(data []byte) uint16 {
  19. var (
  20. sum uint32
  21. length int = len(data)
  22. index int
  23. )
  24. for length &gt; 1 {
  25. sum += uint32(data[index])&lt;&lt;8 + uint32(data[index+1])
  26. index += 2
  27. length -= 2
  28. }
  29. if length &gt; 0 {
  30. sum += uint32(data[index])
  31. }
  32. sum += (sum &gt;&gt; 16)
  33. return uint16(^sum)
  34. }
  35. func main() {
  36. h := Header{
  37. Version: 4,
  38. Len: 20,
  39. TotalLen: 20 + 8,
  40. TTL: 1,
  41. Protocol: 1,
  42. }
  43. argc := len(os.Args)
  44. if argc &lt; 2 {
  45. log.Println(&quot;usage: program + host&quot;)
  46. return
  47. }
  48. ipAddr, _ := net.ResolveIPAddr(&quot;ip&quot;, os.Args[1])
  49. h.Dst = ipAddr.IP
  50. icmpReq := ICMP{
  51. Type: 8,
  52. Code: 0,
  53. Identifier: icmpID,
  54. SeqNo: 1,
  55. }
  56. out, err := h.Marshal()
  57. if err != nil {
  58. log.Println(&quot;ip header error&quot;, err)
  59. return
  60. }
  61. var icmpBuf bytes.Buffer
  62. binary.Write(&amp;icmpBuf, binary.BigEndian, icmpReq)
  63. icmpReq.Checksum = Checksum(icmpBuf.Bytes())
  64. icmpBuf.Reset()
  65. binary.Write(&amp;icmpBuf, binary.BigEndian, icmpReq)
  66. fd, _ := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
  67. addr := syscall.SockaddrInet4{
  68. Port: 0,
  69. }
  70. copy(addr.Addr[:], ipAddr.IP[12:16])
  71. pkg := append(out, icmpBuf.Bytes()...)
  72. if err := syscall.Sendto(fd, pkg, 0, &amp;addr); err != nil {
  73. log.Println(&quot;Sendto err:&quot;, err)
  74. }
  75. laddr, err := net.ResolveIPAddr(&quot;ip4:icmp&quot;, &quot;0.0.0.0&quot;)
  76. if err != nil {
  77. log.Fatal(err)
  78. }
  79. c, err := net.ListenIP(&quot;ip4:icmp&quot;, laddr)
  80. if err != nil {
  81. log.Fatal(err)
  82. }
  83. for {
  84. buf := make([]byte, 2048)
  85. n, raddr, err := c.ReadFrom(buf)
  86. if err != nil {
  87. log.Println(err)
  88. continue
  89. }
  90. icmpType := buf[0]
  91. if icmpType == 11 {
  92. if n == 36 { // Time exceeded messages
  93. // A time exceeded message contain IP header(20 bytes) and first 64 bits of the original payload
  94. id := binary.BigEndian.Uint16(buf[32:34])
  95. log.Println(&quot;recv id&quot;, id)
  96. if id == icmpID {
  97. log.Println(&quot;recv Time Exceeded from&quot;, raddr)
  98. }
  99. }
  100. }
  101. }
  102. }

Actually, I am writing a traceroute in go, if anyone is interested about that, the whole code is in github.

答案2

得分: 1

我认为在创建套接字时,你需要将协议设置为IPPROTO_ICMPraw(7) man page中提到,IPPROTO_RAW套接字只能用于发送。此外,如果你使用IPPROTO_ICMP,就不需要提供IP头部。(注意:我没有在Go中实际尝试过这个。)

英文:

I think you need to give IPPROTO_ICMP as the protocol when you create your socket. The raw(7) man page says that an IPPROTO_RAW socket is send only. Also, if you use IPPROTO_ICMP, you don't give the IP header. (Note: I haven't actually tried this in Go.)

huangapple
  • 本文由 发表于 2016年3月23日 12:31:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/36169890.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定