dongming6201 2013-11-03 02:46
浏览 75
已采纳

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

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 (
    "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)
}

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

  • 写回答

2条回答 默认 最新

  • donglu9898 2013-11-03 03:10
    关注

    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
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 stm32流水灯+呼吸灯+外部中断按键
  • ¥15 将二维数组,按照假设的规定,如0/1/0 == "4",把对应列位置写成一个字符并打印输出该字符
  • ¥15 NX MCD仿真与博途通讯不了啥情况
  • ¥15 win11家庭中文版安装docker遇到Hyper-V启用失败解决办法整理
  • ¥15 gradio的web端页面格式不对的问题
  • ¥15 求大家看看Nonce如何配置
  • ¥15 Matlab怎么求解含参的二重积分?
  • ¥15 苹果手机突然连不上wifi了?
  • ¥15 cgictest.cgi文件无法访问
  • ¥20 删除和修改功能无法调用