代码之家  ›  专栏  ›  技术社区  ›  Cubox

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

  •  1
  • Cubox  · 技术社区  · 11 年前

    我正在做一个ping图书馆,主要是为了好玩。

    我最近在实现中发现了一个错误,我没有检查收到的packed的seq。我现在已经修复了它,如果发生超时,我会丢弃一个数据包。

    但今天,我看到ping实用程序打印收到的回复包,即使它们超时了。

    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
    

    我不知道在我的图书馆里该做什么。我应该保持实际的行为,还是需要调整它以适应“旧”的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)
    }
    

    该库不是为特定用途而制作的。我会用它来做一些项目,但我想知道每个选择的理由。

    正如我所看到的,实施第二种选择将更加困难。

    谢谢 (如果我的帖子不清楚,请不要犹豫,让我澄清,已经晚了。)

    如果要检查项目地址: here

    2 回复  |  直到 11 年前
        1
  •  1
  •   nemo    11 年前

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

    ,我不会这么做。在应用程序中,我不会期望两次数据包,并且我必须手动为这些数据包记账。如果你的图书馆记账,我可以在稍后的时间点询问数据包是否在稍后的某个时间收到,那就可以了。

    因此,要么没有,要么有这样的API:

    notifyReceivedLostPacket(seqId int) chan Packet
    
        2
  •  0
  •   Ask Bjørn Hansen    11 年前

    我将投反对票:

    ,你应该这样做。我们得到了回复;应该报告。应用程序必须弄清楚如何处理它。