基于ICMP协议的ping程序实现

ICMP是(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。 要利用ICMP协议实现ping功能的程序,Ping程序将采取下列步骤:
(1)创建类型为SOCK_RAW的一个套接字,同时设定协议类型为IPPROTO_ICMP。
(2)创建并初始化ICMP头。
(3)调用sendto,将ICMP请求发送给远程主机。
(4)调用recvfrom,以接受任何ICMP响应。

#!/usr/bin/python
# -*- coding:UTF-8 -*-

#导入相关包
import os,socket,struct,select,time		
#struct包用于处理二进制数据,selcet包监听是否有可读、可写或异常事件产生

ICMP_ECHO_REQUEST = 8		# ICMP报文类型
DEFAULT_TIMEOUT = 2 		# 默认超时时间
DEFAULT_COUNT = 4 		# 默认发送报文的数量

class Pinger(object):		#定义Pinger类
	#定义初始化方法
	def __init__(self, target_host, count=DEFAULT_COUNT,timeout=DEFAULT_TIMEOUT):	
		self.target_host = target_host
		self.count = count
		self.timeout = timeout

	def do_checksum(self, source_string):		#计算校验和
		sum = 0		
		max_count = (len(source_string)/2)*2	# 取小于等于len(source_string)最大偶数
		count = 0
		while count < max_count:
			#ord返回单字符的ASCII码
			val = ord(source_string[count + 1])*256 + ord(source_string[count])	
			sum = sum + val	            #累加到sum
			sum = sum & 0xffffffff		#sum取后32位
			count = count + 2
		if max_count<len(source_string):	#若len(source_string)为奇数,加上source_string的最后一位	
			sum = sum + ord(source_string[len(source_string) - 1])
			sum = sum & 0xffffffff
		sum = (sum >> 16) + (sum & 0xffff)	     #将sum的高16位加上低16位
		sum = sum + (sum >> 16)		    #将sum加上sum的高16位
		answer = ~sum			    #将sum取反
		answer = answer & 0xffff	    # 取answer的低16位
		answer = answer >> 8 | (answer << 8 & 0xff00)   # 将answer右移8位与上answer左移8位
		return answer	 # 返回计算得到的校验和

	def send_ping(self, sock, ID):			#发送ICMP报文
		target_addr = socket.gethostbyname(self.target_host) 	# 获得目的 ip 地址
		my_checksum = 0		# 初始化校验和为0
		# 将数据按“bbHHh“ 格式打包 b:integer 1B H:unsigned short 2B h:short 2B
		header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0,my_checksum, ID, 1)
		bytes_In_double = struct.calcsize("d")		 # 得到 ’d’ 格式的数据字节数
		data = (100 - bytes_In_double) * "Q"		 #data 一共 100 字节 发送一些列 ’Q...’
		data = struct.pack("d", time.time()) + data	 # data 前加上时间
		# 根据header和data计算校验和
		my_checksum = self.do_checksum(header + data)
		header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), ID, 1)
		# 打包header 并将校验和由主机字节顺序转为网络字节顺序
		packet = header + data		 # 将header和data组合成packet
		sock.sendto(packet, (target_addr, 1))		 # 向目标地址发送packet

	def receive_ping(self, sock, ID, timeout):		#接收服务器返回的报文
		time_remaining = timeout	# 设置超时时间
		while True:
			start_time = time.time()
			readable = select.select([sock], [], [], time_remaining)	# 监听sock是否有数据可读
			time_spent = (time.time() - start_time)
			if readable[0] == []:		 # 没有可读数据
				return
			time_received = time.time()		# 记录接收的时间
			recv_packet, addr = sock.recvfrom(1024)		 # 接收1024个字节数据
			icmp_header = recv_packet[20:28]	 # 前20字节为IP首部 icmp首部一共8个字节 ‘bbHHh’ 1+1+2+2+2=8
			# 从 icmp_header 中解包
			type, code, checksum, packet_ID, sequence = struct.unpack("bbHHh", icmp_header)		 
			if packet_ID == ID:		# ID匹配
				bytes_In_double = struct.calcsize("d")
				# 从 data 中得到发送的时间
				time_sent = struct.unpack("d", recv_packet[28:28 +bytes_In_double])[0] 		
				return time_received - time_sent	 # 返回所用的时间
			time_remaining = time_remaining - time_spent
			if time_remaining <= 0:		 # 超时返回
				return

	def ping_once(self):			#Ping一次服务器,返回延迟时间
		icmp = socket.getprotobyname("icmp")		 # 得到协议号
		try:
			# 创建原始套接字
			m_sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
		except socket.error, (errno, msg):
			if errno == 1:
				# 没有权限
				msg += "ICMP messages can only be sent from root user processes"
				print msg
			else:
				print errno
		except Exception, e:
			print "Exception: %s" %(e)
		my_ID = os.getpid() & 0xFFFF		# 进程id
		self.send_ping(m_sock, my_ID)		 # 发送报文
		delay = self.receive_ping(m_sock, my_ID, self.timeout)		 # 接收回应
		m_sock.close()
		return delay

	def ping(self):			#实现Ping count
		for i in xrange(self.count):
			print "Ping to %s..." % self.target_host,
			try:
				delay = self.ping_once()	 # 返回延迟时间
			except socket.error, e:
				print "Ping failed. (socket error: '%s')" % e[1]
				break
			if delay == None:
				print "Ping failed. (timeout within %ssec.)" % self.timeout
			else:
				delay = delay * 1000
				print "Get pong in %0.4fms" % delay

if __name__ == '__main__': 					 #Main函数
	target_host='www.runoob.com'				 # 目标主机
	pinger = Pinger(target_host=target_host)		 #创建Ping对象
	pinger.ping()						 # 调用ping方法

需要特别注意的是在Linux操作系统下,会出现下图这种情况!!!
Operation not permittedICMP messages can only be sent from root user processes

基于ICMP协议的ping程序实现_第1张图片

现在加上sudo后就可以ping通了!运行结果如下:

基于ICMP协议的ping程序实现_第2张图片

你可能感兴趣的:(Python)