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

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

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

问题

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

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

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

icmp_seq 2的请求超时
icmp_seq 3的请求超时
来自80.67.169.18的64字节:icmp_seq=2 ttl=58 time=2216.104 ms
来自80.67.169.18的64字节:icmp_seq=3 ttl=58 time=1216.559 ms

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

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

/*
Package libping provide the ability to send ICMP packets easily.
*/
package libping

import (
    "bytes"
    "net"
    "os"
    "time"
)

const (
    ICMP_ECHO_REQUEST = 8
    ICMP_ECHO_REPLY   = 0
)

// The struct Response is the data returned by Pinguntil.
type Response struct {
    Delay       time.Duration
    Error       error
    Destination string
    Seq         int
    Readsize    int
    Writesize   int
}

func makePingRequest(id, seq, pktlen int, filler []byte) []byte {
    p := make([]byte, pktlen)
    copy(p[8:], bytes.Repeat(filler, (pktlen-8)/len(filler)+1))

    p[0] = ICMP_ECHO_REQUEST // type
    p[1] = 0                 // code
    p[2] = 0                 // cksum
    p[3] = 0                 // cksum
    p[4] = uint8(id >> 8)    // id
    p[5] = uint8(id & 0xff)  // id
    p[6] = uint8(seq >> 8)   // sequence
    p[7] = uint8(seq & 0xff) // sequence

    // calculate icmp checksum
    cklen := len(p)
    s := uint32(0)
    for i := 0; i < (cklen - 1); i += 2 {
        s += uint32(p[i+1])<<8 | uint32(p[i])
    }
    if cklen&1 == 1 {
        s += uint32(p[cklen-1])
    }
    s = (s >> 16) + (s & 0xffff)
    s = s + (s >> 16)

    // place checksum back in header; using ^= avoids the
    // assumption the checksum bytes are zero
    p[2] ^= uint8(^s & 0xff)
    p[3] ^= uint8(^s >> 8)

    return p
}

func parsePingReply(p []byte) (id, seq, code int) {
    id = int(p[24])<<8 | int(p[25])
    seq = int(p[26])<<8 | int(p[27])
    code = int(p[21])
    return
}

// Pingonce send one ICMP echo packet to the destination, and return the latency.
// The function is made to be simple. Simple request, simple reply.
func Pingonce(destination string) (time.Duration, error) {
    response := make(chan Response)
    go Pinguntil(destination, 1, response, time.Second)
    answer := <-response
    return answer.Delay, answer.Error
}

// Pinguntil will send ICMP echo packets to the destination until the counter is reached, or forever if the counter is set to 0.
// The replies are given in the Response format.
// You can also adjust the delay between two ICMP echo packets with the variable delay.
func Pinguntil(destination string, count int, response chan Response, delay time.Duration) {
    raddr, err := net.ResolveIPAddr("ip", destination)
    if err != nil {
        response <- Response{Delay: 0, Error: err, Destination: destination, Seq: 0}
        close(response)
        return
    }

    ipconn, err := net.Dial("ip:icmp", raddr.IP.String())
    if err != nil {
        response <- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: 0}
        close(response)
        return
    }

    sendid := os.Getpid() & 0xffff
    pingpktlen := 64
    seq := 0
    var elapsed time.Duration = 0

    for ; seq < count || count == 0; seq++ {
        elapsed = 0
        if seq > 65535 { // The two bytes for seq. Don't overflow!
            seq = 0
        }
        sendpkt := makePingRequest(sendid, seq, pingpktlen, []byte("Go Ping"))

        start := time.Now()

        writesize, err := ipconn.Write(sendpkt)
        if err != nil || writesize != pingpktlen {
            response <- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: 0}
            time.Sleep(delay)
            continue
        }

        ipconn.SetReadDeadline(time.Now().Add(time.Second * 1)) // 1 second

        resp := make([]byte, 1024)
        for {
            readsize, err := ipconn.Read(resp)

            elapsed = time.Now().Sub(start)

            rid, rseq, rcode := parsePingReply(resp)

            if err != nil {
                response <- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: readsize}
                break
            } else if rcode != ICMP_ECHO_REPLY || rseq != seq || rid != sendid {
                continue
            } else {
                response <- Response{Delay: elapsed, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: readsize}
                break
            }
        }
        time.Sleep(delay - elapsed)
    }
    close(response)
}

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

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

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

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

英文:

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.

Request timeout for icmp_seq 2
Request timeout for icmp_seq 3
64 bytes from 80.67.169.18: icmp_seq=2 ttl=58 time=2216.104 ms
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?

/*
Package libping provide the ability to send ICMP packets easily.
*/
package libping

import (
    &quot;bytes&quot;
    &quot;net&quot;
    &quot;os&quot;
    &quot;time&quot;
)

const (
    ICMP_ECHO_REQUEST = 8
    ICMP_ECHO_REPLY   = 0
)

// The struct Response is the data returned by Pinguntil.
type Response struct {
    Delay       time.Duration
    Error       error
    Destination string
    Seq         int
    Readsize    int
    Writesize   int
}

func makePingRequest(id, seq, pktlen int, filler []byte) []byte {
    p := make([]byte, pktlen)
    copy(p[8:], bytes.Repeat(filler, (pktlen-8)/len(filler)+1))

    p[0] = ICMP_ECHO_REQUEST // type
    p[1] = 0                 // code
    p[2] = 0                 // cksum
    p[3] = 0                 // cksum
    p[4] = uint8(id &gt;&gt; 8)    // id
    p[5] = uint8(id &amp; 0xff)  // id
    p[6] = uint8(seq &gt;&gt; 8)   // sequence
    p[7] = uint8(seq &amp; 0xff) // sequence

    // calculate icmp checksum
    cklen := len(p)
    s := uint32(0)
    for i := 0; i &lt; (cklen - 1); i += 2 {
        s += uint32(p[i+1])&lt;&lt;8 | uint32(p[i])
    }
    if cklen&amp;1 == 1 {
        s += uint32(p[cklen-1])
    }
    s = (s &gt;&gt; 16) + (s &amp; 0xffff)
    s = s + (s &gt;&gt; 16)

    // place checksum back in header; using ^= avoids the
    // assumption the checksum bytes are zero
    p[2] ^= uint8(^s &amp; 0xff)
    p[3] ^= uint8(^s &gt;&gt; 8)

    return p
}

func parsePingReply(p []byte) (id, seq, code int) {
    id = int(p[24])&lt;&lt;8 | int(p[25])
    seq = int(p[26])&lt;&lt;8 | int(p[27])
    code = int(p[21])
    return
}

// Pingonce send one ICMP echo packet to the destination, and return the latency.
// The function is made to be simple. Simple request, simple reply.
func Pingonce(destination string) (time.Duration, error) {
    response := make(chan Response)
    go Pinguntil(destination, 1, response, time.Second)
    answer := &lt;-response
    return answer.Delay, answer.Error
}

// Pinguntil will send ICMP echo packets to the destination until the counter is reached, or forever if the counter is set to 0.
// The replies are given in the Response format.
// You can also adjust the delay between two ICMP echo packets with the variable delay.
func Pinguntil(destination string, count int, response chan Response, delay time.Duration) {
    raddr, err := net.ResolveIPAddr(&quot;ip&quot;, destination)
    if err != nil {
        response &lt;- Response{Delay: 0, Error: err, Destination: destination, Seq: 0}
        close(response)
        return
    }

    ipconn, err := net.Dial(&quot;ip:icmp&quot;, raddr.IP.String())
    if err != nil {
        response &lt;- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: 0}
        close(response)
        return
    }

    sendid := os.Getpid() &amp; 0xffff
    pingpktlen := 64
    seq := 0
    var elapsed time.Duration = 0

    for ; seq &lt; count || count == 0; seq++ {
        elapsed = 0
        if seq &gt; 65535 { // The two bytes for seq. Don&#39;t overflow!
            seq = 0
        }
        sendpkt := makePingRequest(sendid, seq, pingpktlen, []byte(&quot;Go Ping&quot;))

        start := time.Now()

        writesize, err := ipconn.Write(sendpkt)
        if err != nil || writesize != pingpktlen {
            response &lt;- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: 0}
            time.Sleep(delay)
            continue
        }

        ipconn.SetReadDeadline(time.Now().Add(time.Second * 1)) // 1 second

        resp := make([]byte, 1024)
        for {
            readsize, err := ipconn.Read(resp)

            elapsed = time.Now().Sub(start)

            rid, rseq, rcode := parsePingReply(resp)

            if err != nil {
                response &lt;- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: readsize}
                break
            } else if rcode != ICMP_ECHO_REPLY || rseq != seq || rid != sendid {
                continue
            } else {
                response &lt;- Response{Delay: elapsed, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: readsize}
                break
            }
        }
        time.Sleep(delay - elapsed)
    }
    close(response)
}

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:

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:

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:

确定