您的当前位置:首页正文

Ping实现

来源:要发发知识网

ping是常见的一个网络检测工具,牵涉到的核心协议是ICMP协议和IP头协议,下面橙色背景的字段为需要关心的部分

流程

图1

icmp_header

图2
参考
客户端发送的数据中:
  • type必须设置为8
  • id为客户端唯一标识(范例求简硬编码1)
  • sequence为客户端数据包序列号一般自增1(范例求简硬编码1)
  • checksum为16位为单位的二进制反码求和(天书一样,看不懂就GG照抄吧)
USHORT ChechkSum(USHORT *buffer, int size)
{
   DWORD chkSum = 0;
 
   while(size > 1)
   {
      chkSum += *buffer++;
      size -= sizeof(USHORT);
   }
 
   if(size)
      chkSum += *(UCHAR*)buffer;
 
   chkSum = (chkSum >> 16) + (chkSum & 0xffff); 
   chkSum += (chkSum >> 16);
 
   return (USHORT)(~chkSum);
}

ip_header

图3
参考
  • ihl为服务器返回的ip_header包长度(注意哦,协议定义ihl是以4字节为单位,所以实际运算我们需要*4),pointer+ihl即icmp_header包开始的位置
  • ttl
Paste_Image.png

我们发送给服务器的icmp_header里面把timestamp设置为当前时间(毫秒级),服务器返回的icmp_header会把这个timestamp原封不动返回回来,我们再用当前时间去减这个时间,就是每一次ping的时间

python实现

import struct
import ctypes
import socket
import time

def __checksum(buf):
    size = len(buf)
    position = 0
    result = 0
    while size>1:
        result += struct.unpack('H',buf[position:position+2])[0]
        position += 2
        size -= 2
    if size:
        result += struct.unpack('H',buf[position:position+2])[0]
    result = (result>>16) + (result&0xffff)
    result += (result>>16)
    return ctypes.c_ushort(~result).value

class DataIO(object):
    def __init__(self,datagram):
        self.datagram = datagram
        self.position = 0
        self.length = len(self.datagram)
    @property
    def bytesAvailable(self):
        return self.length-self.position
    def read_datagram(self,count):
        data = self.datagram[self.position:self.position+count]
        self.position += count
        return data
    
class IPHeader():
    version_ihl = 0
    dscp_ecn = 0
    total_length = 0
    identification = 0
    offset = 0
    ttl = 0
    protocol = 0
    checksum = 0
    source_address = 0
    dest_address = 0
    def deserialize(self,src):
        io = DataIO(src)
        self.version_ihl = struct.unpack('B',io.read_datagram(1))[0]
        self.dscp_ecn = struct.unpack('B',io.read_datagram(1))[0]
        self.total_length = struct.unpack('H',io.read_datagram(2))[0]
        self.identification = struct.unpack('H',io.read_datagram(2))[0]
        self.offset = struct.unpack('H',io.read_datagram(2))[0]
        self.ttl = struct.unpack('B',io.read_datagram(1))[0]
        self.protocol = struct.unpack('B',io.read_datagram(1))[0]
        self.checksum = struct.unpack('H',io.read_datagram(2))[0]
        self.source_address = struct.unpack('L',io.read_datagram(4))[0]
        self.dest_address = struct.unpack('L',io.read_datagram(4))[0]
    @property
    def ihl(self):
        return (self.version_ihl & 0x0f)*4
    
class ICMPHeader():
    type = 0
    code = 0
    checksum = 0
    id = 0
    sequence = 0
    timestamp = 0
    def serialize(self):
        return ''.join((
                        struct.pack('B',self.type),
                        struct.pack('B',self.code),
                        struct.pack('H',self.checksum),
                        struct.pack('H',self.id),
                        struct.pack('H',self.sequence),
                        struct.pack('L',self.timestamp),
                        ))
    def deserialize(self,src):
        io = DataIO(src)
        self.type = struct.unpack('B',io.read_datagram(1))[0]
        self.code = struct.unpack('B',io.read_datagram(1))[0]
        self.checksum = struct.unpack('H',io.read_datagram(2))[0]
        self.id = struct.unpack('H',io.read_datagram(2))[0]
        self.sequence = struct.unpack('H',io.read_datagram(2))[0]
        self.timestamp = struct.unpack('L',io.read_datagram(4))[0]

def ping(host,timeout=1):
    sock = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket.IPPROTO_ICMP)
    header = ICMPHeader()
    header.type = 8
    header.id = 1
    header.sequence = 1
    header.timestamp = time.time()
    header.checksum = __checksum(header.serialize())
    sock.sendto(header.serialize(),(socket.gethostbyname(host),0))
    try:
        sock.settimeout(timeout)
        data = sock.recvfrom(256)[0]
        resp_ipheader = IPHeader()
        resp_ipheader.deserialize(data)
        resp_header = ICMPHeader()
        resp_header.deserialize(data[resp_ipheader.ihl:])
        span = (time.time()-header.timestamp)*1000
        return int(span),resp_ipheader.ttl
    except socket.timeout:
        pass

if __name__=='__main__':
    for i in range(4):
        if i:
            time.sleep(1)
        print 

golang实现

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "net"
    "time"
    "unsafe"
)

func __checksum(data []byte) uint16 {
    length := len(data)
    position := 0
    var sum uint32 = 0
    for length > 1 {
        sum += uint32(data[position]) + uint32(data[position+1])<<8
        position += 2
        length -= 2
    }
    if length > 0 {
        sum += uint32(data[position])
    }
    sum += (sum >> 16)
    return uint16(^sum)
}
func __now() uint32 {
    return uint32(time.Now().UnixNano() / (1000 * 1000))
}

type IPHeader struct {
    version_ihl    byte
    dscp_ecn       byte
    total_length   uint16
    identification uint16
    offset         uint16
    ttl            byte
    protocol       byte
    checksum       uint16
    source_address uint32
    dest_address   uint32
}

func (self IPHeader) ihl() byte {
    return (self.version_ihl & 0x0f) * 4
}

type ICMPHeader struct {
    _type     byte
    code      byte
    checksum  uint16
    id        uint16
    sequence  uint16
    timestamp uint32
}

func (self ICMPHeader) data() []byte {
    buffer := bytes.Buffer{}
    binary.Write(&buffer, binary.LittleEndian, self)
    return buffer.Bytes()
}

type PingResponse struct {
    ttl  uint32
    span uint32
}

func ping(host string, timeout time.Duration) (*PingResponse, error) {
    conn, err := net.Dial("ip4:icmp", host)
    if err != nil {
        return nil, err
    }
    conn.SetReadDeadline(time.Now().Add(time.Second * timeout))
    header := ICMPHeader{
        _type:     8,
        id:        1,
        sequence:  1,
        timestamp: __now(),
    }
    header.checksum = __checksum(header.data())
    conn.Write(header.data())
    var data [256]byte
    _, err = conn.Read(data[0:])
    if err != nil {
        return nil, err
    }
    ip_header := (*IPHeader)(unsafe.Pointer(&data[0]))
    icmp_header := (*ICMPHeader)(unsafe.Pointer(&data[ip_header.ihl()]))
    return &PingResponse{
        ttl:  uint32(ip_header.ttl),
        span: __now() - icmp_header.timestamp,
    }, nil
}

func main() {
     3))
}