赞
踩
在网络日益发达的今天,网络变得越来越重要,而ping命令是我们使用的比较多的用于测试网络连通性的命令,ping用于确定本地主机是否能与另一台主机成功交换(发送与接收)数据包,再根据返回的信息,就可以推断TCP/IP参数是否设置正确,以及运行是否正常、网络是否通畅等。因此,用好ping命令就要先理解ping命令的含义,掌握网络协议的基本实现方法。
关键词:icmp,python,ping,wireshark 流量分析,网络编程
系统环境:Windows10
DOS的ping命令结果:
输出一:
将ping目标主机进行DNS解析,获取对应的ip地址,并向该主机发送32字节大小的数据包。
输出二:
来自目标主机的响应信息:
输出三:
ping响应概览:
源地址:10.77.170.136
目标地址:14.215.177.38 (www.baidu.com)
在wireshark 过滤器中设置好筛选条件,发现抓取到了4组请求与响应。
发现ping命令主要基于ICMP(Internet Control Message Protocol)Internet控制报文协议实现,它包含了两部分:ping命令发送一个ICMP回显请求报文给目标主机,目标主机接收到之后, 会返回一个ICMP回显响应报文。
查看第一个ICMP回显请求报文,Type(类型)为8,Code(代码)为0,Identifier(标识符)为发送进程的id,Data(数据包)为32字节数据。
查看第一个ICMP回显响应报文,Type(类型)为0,Code(代码)为0,Identifier(标识符)与第一个ICMP回显请求报文一致。Response time(响应时间)为5.893ms。
ICMP协议不是高层协议,而是IP层的协议,因为ICMP报文装在IP数据报中,作为其中的数据部分。ICMP报文作为IP层数据包的数据,加上数据报的首部,组成IP数据报发送出去。
ICMP报文格式:
操作系统:Windows10
开发环境:python,pycharm
开发所用库:socket、select、struct、os、time
根据ping命令流程,编写5个模块:
使用socket模块来创建套接字,套接字类型选择基于网络的原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以。
def connect(addr, icmp_packet):
# 实例化一个socket对象,ipv4,原始套接字,分配协议端口
rawsocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
# 发送数据到网络
rawsocket.sendto(icmp_packet, (addr, 800))
# 返回数据
return rawsocket, addr
根据ICMP数据报的特点,在本地构造对应的数据包,并使用struct模块将其打包成计算机可识别的二进制数据流。struct模块类似于C语言中的结构体。
def request(seq):
type = 8
code = 0
check_sum = 0
ID = os.getpid() & 0xffff
#data = time.time()
data = b'abcdefghijklmnopqrstuvwabcdefghi'
# 把字节打包成二进制数据,其中>(!)代表是是大端模式,B代表的是一个字节的无符号整数,H代表的是两个字节的无符号整数,d代表的是double
icmp_packet = struct.pack('>BBHHH32s', type, code, check_sum, ID, seq, data)
check_sum = checksum(icmp_packet) # 获取校验和
# 把校验和传入,再次打包
icmp_packet = struct.pack('>BBHHH32s', type, code, check_sum, ID, seq, data)
return icmp_packet
使用select模块监听rawsocket套接字,并接收来自rawsocket的数据报。使用struct模块进行解包,根据IP数据报和ICMP数据报的结构,截取所需要的内容。
def reply(rawsocket, ping_time, timeout=1):
while True:
# 开始时间
started_time = time.time()
# 实例化select对象,可读rawsocket,可写为空,可执行为空,超时时间
recv = select.select([rawsocket], [], [], timeout)
# 等待时间
wait_for_time = (time.time() - started_time)
# 如果没有返回可写内容判断为超时
if recv[0] == []:
return -1, -1
# 记录接收时间
received_time = time.time()
# 设置接收的包的字节为1024字节
received_packet, addr = rawsocket.recvfrom(1024)
# 判断是否超时
timeout = timeout - wait_for_time
if timeout <= 0:
return -1, -1, -1
#获取ip报文的TTL
ttl = received_packet[8]
# 获取icmp报文的头部
icmpHeader = received_packet[20:28]
#print(received_packet)
type, code, checksum, packet_id, sequence = struct.unpack(">BBHHH", icmpHeader)
if type == 0:
return received_time - ping_time, sequence, ttl
校验和的计算主要有如下步骤:
def checksum(data):
n = len(data)
m = n % 2
sum = 0
for i in range(0, n - m, 2):
# 传入data以每两个字节(十六进制),第一字节在低位,第二个字节在高位
sum += (data[i]) + ((data[i + 1]) << 8)
if m:
sum += (data[-1])
# 将高于16位与低16位相加
while sum >> 16:
sum = (sum >> 16) + (sum & 0xffff)
# 如果还有高于16位,将继续与低16位相加
answer = ~sum & 0xffff
# 主机字节序转网络字节序列
answer = answer >> 8 | (answer << 8 & 0xff00)
return answer
通过socket模块中的gethostbyname函数获取域名的ip地址,利用请求request函数返回icmp_packet数据包,并使用连接connect函数连接目标主机,同时利用响应reply函数接收ICMP回显响应报文,最后输出相关信息与统计信息。
def ping(host, count = 4, timeout = 2):
addr = socket.gethostbyname(host) # 获取ip地址
print("正在 Ping {0} [{1}] 具有 32 字节的数据:".format(host, addr))
lost = 0
accept = 0
sumtime = 0.0
times = [] # 统计所有包的往返时间长度
for i in range(count):
i += 1
icmp_packet = request(i)
# print(icmp_packet)
rawsocket, dst_addr = connect(addr, icmp_packet)
time0, sequence, ttl = reply(rawsocket, time.time(), timeout)
if time0 < 0:
print("请求超时")
lost += 1
times.append(timeout * 1000)
else:
time0 = time0 * 1000
print("来自 {0} 的回复: 字节=32 seq = {1} 时间={2:.2f}ms TTL={3}".format(dst_addr, sequence, time0, ttl))
accept += 1
sumtime += time0
times.append(time0)
# 统计
print('{0} 的 Ping 统计信息:'.format(addr))
print('\t数据包: 已发送 = {0},已接收 = {1},丢失 = {2} ({3}% 丢失),'
.format(count, accept, lost, lost // (lost + accept) * 100,))
print('往返行程的估计时间(以毫秒为单位):')
print('\t最短 = {0:.2f}ms,最长 = {1:.2f}ms,平均 = {2:.2f}ms'
.format(min(times),max(times), sum(times) // (lost + accept)))
如图,与预期结果相差不大,成功实现ping命令程序。
随着网络技术的不断进步,通过网络通信协议实现的功能越来越多,ping命令只是其中的一个简单的应用,通过socket的raw_socket类型,还可以自定义数据报进行更底层应用的开发。
此外,可以编写ping程序来进行dos攻击或ddos攻击。例如本文中的程序,将ping 的目标设置为拟进行攻击目标,使用循环来实现不间断地发送数据包,即完成了dos攻击。若使用了大量的肉鸡服完成攻击的话,这便是ddos攻击,破坏力极大。
[1] 谢希仁. 《计算机网络》. 第7版. 电子工业出版社. 2017年出版
[2]Ping程序设计 : https://blog.csdn.net/zha_ojunchen/article/details/106225905
[3]python socket编程详细介绍 : https://blog.csdn.net/rebelqsp/article/details/22109925
[4]python之struct详解 : https://blog.csdn.net/qq_30638831/article/details/80421019
[5]Ping 本质( ICMP ) : https://www.jianshu.com/p/e1795962ad76
[6]IP、ICMP、UDP、TCP 校验和算法 : https://blog.csdn.net/weiweiliulu/article/details/20531367
[7]select — Waiting for I/O completion : https://docs.python.org/3/library/select.html#select.select
[8]IP Protocol Header Fundamentals Explained with Diagrams : https://www.thegeekstuff.com/2012/03/ip-protocol-header/
[9] How to Calculate IP Header Checksum : https://www.thegeekstuff.com/2012/05/ip-header-checksum/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。