ping是常见的一个网络检测工具,牵涉到的核心协议是ICMP协议和IP头协议,下面橙色背景的字段为需要关心的部分
流程
icmp_header
参考https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol
客户端发送的数据中:
- 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
参考https://en.wikipedia.org/wiki/IPv4
- ihl为服务器返回的ip_header包长度(注意哦,协议定义ihl是以4字节为单位,所以实际运算我们需要*4),pointer+ihl即icmp_header包开始的位置
- ttl
我们发送给服务器的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 ping('www.baidu.com')
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() {
fmt.Println(ping("www.baidu.com", 3))
}