制作一个ping库。我应该遵循实际的ping行为吗?

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

Making a ping library. Should I follow the actual ping behaviour?

问题

我正在制作一个ping库,主要是为了好玩。

最近我在实现中发现了一个bug,即我没有检查接收到的数据包的序列号。我现在通过丢弃超时的数据包来修复了这个问题。

但是今天,我发现ping实用程序会打印接收到的回复数据包,即使它们超时了。

  1. icmp_seq 2的请求超时
  2. icmp_seq 3的请求超时
  3. 来自80.67.169.1864字节:icmp_seq=2 ttl=58 time=2216.104 ms
  4. 来自80.67.169.1864字节:icmp_seq=3 ttl=58 time=1216.559 ms

我不知道在我的库中该怎么做。我应该保持当前的行为,还是需要调整为“旧”的ping方式?

以下是代码部分,不需要翻译:

  1. /*
  2. Package libping provide the ability to send ICMP packets easily.
  3. */
  4. package libping
  5. import (
  6. "bytes"
  7. "net"
  8. "os"
  9. "time"
  10. )
  11. const (
  12. ICMP_ECHO_REQUEST = 8
  13. ICMP_ECHO_REPLY = 0
  14. )
  15. // The struct Response is the data returned by Pinguntil.
  16. type Response struct {
  17. Delay time.Duration
  18. Error error
  19. Destination string
  20. Seq int
  21. Readsize int
  22. Writesize int
  23. }
  24. func makePingRequest(id, seq, pktlen int, filler []byte) []byte {
  25. p := make([]byte, pktlen)
  26. copy(p[8:], bytes.Repeat(filler, (pktlen-8)/len(filler)+1))
  27. p[0] = ICMP_ECHO_REQUEST // type
  28. p[1] = 0 // code
  29. p[2] = 0 // cksum
  30. p[3] = 0 // cksum
  31. p[4] = uint8(id >> 8) // id
  32. p[5] = uint8(id & 0xff) // id
  33. p[6] = uint8(seq >> 8) // sequence
  34. p[7] = uint8(seq & 0xff) // sequence
  35. // calculate icmp checksum
  36. cklen := len(p)
  37. s := uint32(0)
  38. for i := 0; i < (cklen - 1); i += 2 {
  39. s += uint32(p[i+1])<<8 | uint32(p[i])
  40. }
  41. if cklen&1 == 1 {
  42. s += uint32(p[cklen-1])
  43. }
  44. s = (s >> 16) + (s & 0xffff)
  45. s = s + (s >> 16)
  46. // place checksum back in header; using ^= avoids the
  47. // assumption the checksum bytes are zero
  48. p[2] ^= uint8(^s & 0xff)
  49. p[3] ^= uint8(^s >> 8)
  50. return p
  51. }
  52. func parsePingReply(p []byte) (id, seq, code int) {
  53. id = int(p[24])<<8 | int(p[25])
  54. seq = int(p[26])<<8 | int(p[27])
  55. code = int(p[21])
  56. return
  57. }
  58. // Pingonce send one ICMP echo packet to the destination, and return the latency.
  59. // The function is made to be simple. Simple request, simple reply.
  60. func Pingonce(destination string) (time.Duration, error) {
  61. response := make(chan Response)
  62. go Pinguntil(destination, 1, response, time.Second)
  63. answer := <-response
  64. return answer.Delay, answer.Error
  65. }
  66. // Pinguntil will send ICMP echo packets to the destination until the counter is reached, or forever if the counter is set to 0.
  67. // The replies are given in the Response format.
  68. // You can also adjust the delay between two ICMP echo packets with the variable delay.
  69. func Pinguntil(destination string, count int, response chan Response, delay time.Duration) {
  70. raddr, err := net.ResolveIPAddr("ip", destination)
  71. if err != nil {
  72. response <- Response{Delay: 0, Error: err, Destination: destination, Seq: 0}
  73. close(response)
  74. return
  75. }
  76. ipconn, err := net.Dial("ip:icmp", raddr.IP.String())
  77. if err != nil {
  78. response <- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: 0}
  79. close(response)
  80. return
  81. }
  82. sendid := os.Getpid() & 0xffff
  83. pingpktlen := 64
  84. seq := 0
  85. var elapsed time.Duration = 0
  86. for ; seq < count || count == 0; seq++ {
  87. elapsed = 0
  88. if seq > 65535 { // The two bytes for seq. Don't overflow!
  89. seq = 0
  90. }
  91. sendpkt := makePingRequest(sendid, seq, pingpktlen, []byte("Go Ping"))
  92. start := time.Now()
  93. writesize, err := ipconn.Write(sendpkt)
  94. if err != nil || writesize != pingpktlen {
  95. response <- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: 0}
  96. time.Sleep(delay)
  97. continue
  98. }
  99. ipconn.SetReadDeadline(time.Now().Add(time.Second * 1)) // 1 second
  100. resp := make([]byte, 1024)
  101. for {
  102. readsize, err := ipconn.Read(resp)
  103. elapsed = time.Now().Sub(start)
  104. rid, rseq, rcode := parsePingReply(resp)
  105. if err != nil {
  106. response <- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: readsize}
  107. break
  108. } else if rcode != ICMP_ECHO_REPLY || rseq != seq || rid != sendid {
  109. continue
  110. } else {
  111. response <- Response{Delay: elapsed, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: readsize}
  112. break
  113. }
  114. }
  115. time.Sleep(delay - elapsed)
  116. }
  117. close(response)
  118. }

该库并不是为特定的用途而设计的。我将用它来完成一些项目,但我想知道每种选择的理由。

我注意到,实现第二个选项会更加困难。

谢谢!(如果我的帖子不清楚,请随时要求我澄清,现在已经很晚了。)

如果你想检查项目地址:这里

英文:

I am making a ping library, mainly for fun.

I recently found a bug in my implementation, where I did not checked the seq of the received packed. I have now fixed it by discarding a packet if the timeout have occured.

But today, I saw that the ping utility print the received reply packet, even if they got a timeout.

  1. Request timeout for icmp_seq 2
  2. Request timeout for icmp_seq 3
  3. 64 bytes from 80.67.169.18: icmp_seq=2 ttl=58 time=2216.104 ms
  4. 64 bytes from 80.67.169.18: icmp_seq=3 ttl=58 time=1216.559 ms

I don't know what to do in my library. Should I keep the actual behaviour, or do I need to adjust it to the "old" ping way?

  1. /*
  2. Package libping provide the ability to send ICMP packets easily.
  3. */
  4. package libping
  5. import (
  6. &quot;bytes&quot;
  7. &quot;net&quot;
  8. &quot;os&quot;
  9. &quot;time&quot;
  10. )
  11. const (
  12. ICMP_ECHO_REQUEST = 8
  13. ICMP_ECHO_REPLY = 0
  14. )
  15. // The struct Response is the data returned by Pinguntil.
  16. type Response struct {
  17. Delay time.Duration
  18. Error error
  19. Destination string
  20. Seq int
  21. Readsize int
  22. Writesize int
  23. }
  24. func makePingRequest(id, seq, pktlen int, filler []byte) []byte {
  25. p := make([]byte, pktlen)
  26. copy(p[8:], bytes.Repeat(filler, (pktlen-8)/len(filler)+1))
  27. p[0] = ICMP_ECHO_REQUEST // type
  28. p[1] = 0 // code
  29. p[2] = 0 // cksum
  30. p[3] = 0 // cksum
  31. p[4] = uint8(id &gt;&gt; 8) // id
  32. p[5] = uint8(id &amp; 0xff) // id
  33. p[6] = uint8(seq &gt;&gt; 8) // sequence
  34. p[7] = uint8(seq &amp; 0xff) // sequence
  35. // calculate icmp checksum
  36. cklen := len(p)
  37. s := uint32(0)
  38. for i := 0; i &lt; (cklen - 1); i += 2 {
  39. s += uint32(p[i+1])&lt;&lt;8 | uint32(p[i])
  40. }
  41. if cklen&amp;1 == 1 {
  42. s += uint32(p[cklen-1])
  43. }
  44. s = (s &gt;&gt; 16) + (s &amp; 0xffff)
  45. s = s + (s &gt;&gt; 16)
  46. // place checksum back in header; using ^= avoids the
  47. // assumption the checksum bytes are zero
  48. p[2] ^= uint8(^s &amp; 0xff)
  49. p[3] ^= uint8(^s &gt;&gt; 8)
  50. return p
  51. }
  52. func parsePingReply(p []byte) (id, seq, code int) {
  53. id = int(p[24])&lt;&lt;8 | int(p[25])
  54. seq = int(p[26])&lt;&lt;8 | int(p[27])
  55. code = int(p[21])
  56. return
  57. }
  58. // Pingonce send one ICMP echo packet to the destination, and return the latency.
  59. // The function is made to be simple. Simple request, simple reply.
  60. func Pingonce(destination string) (time.Duration, error) {
  61. response := make(chan Response)
  62. go Pinguntil(destination, 1, response, time.Second)
  63. answer := &lt;-response
  64. return answer.Delay, answer.Error
  65. }
  66. // Pinguntil will send ICMP echo packets to the destination until the counter is reached, or forever if the counter is set to 0.
  67. // The replies are given in the Response format.
  68. // You can also adjust the delay between two ICMP echo packets with the variable delay.
  69. func Pinguntil(destination string, count int, response chan Response, delay time.Duration) {
  70. raddr, err := net.ResolveIPAddr(&quot;ip&quot;, destination)
  71. if err != nil {
  72. response &lt;- Response{Delay: 0, Error: err, Destination: destination, Seq: 0}
  73. close(response)
  74. return
  75. }
  76. ipconn, err := net.Dial(&quot;ip:icmp&quot;, raddr.IP.String())
  77. if err != nil {
  78. response &lt;- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: 0}
  79. close(response)
  80. return
  81. }
  82. sendid := os.Getpid() &amp; 0xffff
  83. pingpktlen := 64
  84. seq := 0
  85. var elapsed time.Duration = 0
  86. for ; seq &lt; count || count == 0; seq++ {
  87. elapsed = 0
  88. if seq &gt; 65535 { // The two bytes for seq. Don&#39;t overflow!
  89. seq = 0
  90. }
  91. sendpkt := makePingRequest(sendid, seq, pingpktlen, []byte(&quot;Go Ping&quot;))
  92. start := time.Now()
  93. writesize, err := ipconn.Write(sendpkt)
  94. if err != nil || writesize != pingpktlen {
  95. response &lt;- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: 0}
  96. time.Sleep(delay)
  97. continue
  98. }
  99. ipconn.SetReadDeadline(time.Now().Add(time.Second * 1)) // 1 second
  100. resp := make([]byte, 1024)
  101. for {
  102. readsize, err := ipconn.Read(resp)
  103. elapsed = time.Now().Sub(start)
  104. rid, rseq, rcode := parsePingReply(resp)
  105. if err != nil {
  106. response &lt;- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: readsize}
  107. break
  108. } else if rcode != ICMP_ECHO_REPLY || rseq != seq || rid != sendid {
  109. continue
  110. } else {
  111. response &lt;- Response{Delay: elapsed, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: readsize}
  112. break
  113. }
  114. }
  115. time.Sleep(delay - elapsed)
  116. }
  117. close(response)
  118. }

The library is not made for a specitif usage. I will use it to do a few projects, but I want to know the arguments for each choices.

And as I looked, implementing the second option will be way harder.

Thanks!
(If my post is not clear, don't hesitate to ask me to clarify, it's late.)

If you want to check the project address : here

答案1

得分: 1

我理解你的问题是:“我应该向用户报告已经报告为超时的数据包吗?”。

,我不会这样做。在一个应用程序中,我不会期望收到两次数据包,而且我必须手动进行这些数据包的记录。如果你的库可以进行记录,并且我可以在以后的某个时间点询问该数据包是否在稍后的时间收到,那就可以。

所以要么不报告,要么提供类似以下的API:

  1. notifyReceivedLostPacket(seqId int) chan Packet
英文:

I understood your question to be: 'Should I report packets to the user that I have already reported as being timed out'.

No, I would not do this. In an application I would not expect packets twice and I would have to do the bookkeeping for these manually. If your library does the bookkeeping and I can ask on a later point in time whether the packet was received some later time, that would be OK.

So either no or some API like this:

  1. notifyReceivedLostPacket(seqId int) chan Packet

答案2

得分: 0

我会做相反的投票:

是的,你应该这样做。我们收到了回应,应该进行报告。应用程序将需要决定如何处理它。

询问

英文:

I'll make the opposite vote:

Yes, you should do this. We got a response back; it should be reported. The application will have to figure out what to do with it.

Ask

huangapple
  • 本文由 发表于 2013年11月3日 10:46:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/19749336.html
匿名

发表评论

匿名网友

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

确定