一.课题概述。

一学期一次的课程设计终于开始了(停课两周,马上放寒假了,哈哈哈哈哈哈。。。)这次我们课程设计的科目是计算机协议,我们小组抽到的题目是利用ICMP模仿ping命令写一个主机存活探测的工具。具体描述和需求如下:
【实验目的】
       1. 加深对ICMP协议的理解
       2. 掌握原始套接字进行网络程序设计的方法
【案例描述】
       Ping工具是使用ICMP协议进行网络连通性检测的工具,在日常生活中使用广泛。请根据ICMP协议的相关原理,使用开发工具为同学开发一个Ping工具。
【需求分析】
       根据案例描述,可以总结出用户有以下需求:
       1. 使用该工具可以测试目标主机的状态
       根据ICMP回显请求和回显应答报文,使用该工具测试目标主机的状态。
       2. 程序应该提供帮助信息
       为了方便用户使用,该工具应该提供帮助信息,当用户需要帮助时可以进行查询。
【总体设计】
       1. 使用原始套接字
       由于该工具需要发送和接收ICMP报文,所以应该使用原始套接字。
       2. 发送ICMP回显请求报文
       可以发送ICMP回显请求报文给目的主机
       3. 接收ICMP回显应答报文
       可以接收来自目的主机的ICMP回显应答报文。
       4. 程序应该提供帮助信息
       为了方便用户使用,该工具应该提供帮助信息。可以在程序中设计一个用于提供帮助的函数,当用户需要帮助时调用该函数。

二、分析。

题目不是很难,在正式开始之前要首先弄清楚一个概念什么是原始套接字和标准套接字?它们有什么不同?

  • 标准套接字,指在传输层下面使用的套接字。流式套接字和数据报套接字这两种套接字工作在传输层,主要为应用层的应用程序提供服务,并且在接收和发送时只能操作数据部分,而不能对IP首部或TCP和UDP首部进行操作,通常把这两种套接字称为标准套接字。
  • 原始套接字,它是一种对原始网络报文进行处理的套接字。原始套接字的用途主要有:
    (1)发送自定义的IP 数据报
    (2)发送ICMP 数据报
    (3)网卡的侦听模式,监听网络上的数据包。
    (4)伪装IP地址。
    (5)自定义协议的实现。
    比如发送一个自定义的IP包、UDP包、TCP包或ICMP包,捕获所有经过本机网卡的数据包,伪装本机的IP,想要操作IP首部或传输层协议首部,等等。

弄清楚这个概念后,我们主要在学习怎样构造ICMP Request报文。先学习ICMP Request报文的格式如下图:
              
下表是各字段的含义:

字段 长度 含义
Type 1字节 消息类型: - 0:回显应答报文 - 8:请求回显报文
Code 1字节 消息代码,此处值为0。
Checksum 2字节 检验和。
Identifier 2字节 标识符,发送端标示此发送的报文
Sequence Number 2字节 序列号,发送端发送的报文的顺序号。每发送一次顺序号就加1。
Data 可变 选项数据,是一个可变长的字段,其中包含要返回给发送者的数据。回显应答通常返回与所收到的数据完全相同的数据。

三、代码实现。

1.代码结构。


       main.py用来实现各个代码的调用和命令行化,networkscan实现单个主机的探测,networkscans实现网段探测主机存活数,readlogs实现读取日志文件,logs是日志文件,下图是主要功能代码的各个方法。

2.主要功能分析。

首先是代码使用到的模块。

import os
import struct
import array
import time
import socket
import logging
from queue import Queue
  • struct:生成用于网络传输的而二进制数据。
  • array:是python中实现的一种高效的数组存储类型。
  • socket:套接字。
  • logging:主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等。
  • Queue:实现了一个基本的先进先出(FIFO)容器,使用put()将元素添加到序列尾端,get()从队列尾部移除元素。

代码主要用两个类来实现其中SendPing类中的run()方法主要用来实现对自定义ICMP数据包的发送。

class SendPing():'''发送ICMP请求报文的线程。参数:ipPool      -- IP地址icmpPacket  -- 构造的icmp报文icmpSocket  -- icmp套字接timeout     -- 设置发送超时'''def __init__(self, ipPool, icmpPacket, icmpSocket, timeout=3):self.Sock = icmpSocketself.ipPool = ipPoolself.packet = icmpPacketself.timeout = timeoutself.Sock.settimeout( timeout + 3 )def run(self):try:self.Sock.sendto(self.packet, (self.ipPool, 0))except OSError:print("你不能输入一个内部保留地址!")exit()
def makeIpPool(self, startIP, lastIP):'''生产 IP 地址池'''IPver = 6 if self.IPv6 else 4intIP = lambda ip: IPy.IP(ip).int()  #将IP地址转换为整型格式ipPool = {IPy.intToIp(ip, IPver) for ip in range(intIP(startIP), intIP(lastIP)+1)}return {ip for ip in ipPool if self.isUnIP(ip)}

NetworkScan()方法用来实现探测过程, isUnIP()主要用来判断IP地址的合法性,mPing方法中又调用了__icmpSocket和__icmpPacket方法来建立和构造套接字和ICMP数据包,再通过调SendPing类实现发送给目标主机。

# -*- coding: utf-8 -*-
import os
import struct
import array #array模块是python中实现的一种高效的数组存储类型。
import time
import socket
import logging
from queue import Queue'''
Queue类实现了一个基本的先进先出(FIFO)容器,使用put()将元素添加到序列尾端,get()从队列尾部移除元素。
''''''
filename:指定日志文件名;filemode:和file函数意义相同,指定日志文件的打开模式,'w'或者'a';format:指定输出的格式和内容,format可以输出很多有用的信息,
'''logging.basicConfig(level=logging.INFO, format="[%(asctime)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S",filename="logs", filemode="a")class SendPing():'''发送ICMP请求报文的线程。参数:ipPool      -- 可迭代的IP地址池icmpPacket  -- 构造的icmp报文icmpSocket  -- icmp套字接timeout     -- 设置发送超时'''def __init__(self, ip, icmpPacket, icmpSocket, timeout=3):self.Sock = icmpSocketself.ip = ipself.packet = icmpPacketself.timeout = timeoutself.Sock.settimeout( timeout + 3 )def run(self):try:self.Sock.sendto(self.packet, (self.ip, 0))except OSError:print("你不能输入一个内部保留地址!")exit()class NetworkScan():'''参数:timeout    -- Socket超时,默认3秒IPv6       -- 是否是IPv6,默认为False'''def __init__(self, timeout=3, IPv6=False):self.timeout = timeoutself.IPv6 = IPv6self._LOGS = Queue()'''按照给定的格式(fmt),把数据转换成字符串(字节流),并将该字符串返回.'''self.__data = struct.pack('d', time.time())   #用于ICMP报文的负荷字节(8bit)self.__id = os.getpid()   #构造ICMP报文的ID字段,无实际意义@property   #属性装饰器def __icmpSocket(self):'''socket.getprotobyname('icmp')创建ICMP Socket'''if not self.IPv6:#socket.SOCK_RAW 原始套接字#作用:获得网络协议名(如:'icmp')对应的值Sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))else:Sock = socket.socket(socket.AF_INET6, socket.SOCK_RAW, socket.getprotobyname("ipv6-icmp"))return Sockdef __inCksum(self, packet):'''ICMP 报文效验和计算方法& 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0'''if len(packet) & 1:packet = packet + '\\0'words = array.array('h', packet)sum = 0for word in words:sum += (word & 0xffff)'''右移动运算符:把">>"左边的运算数的各二进位全部右移若干位,>> 右边的数字指定了移动的位数'''sum = (sum >> 16) + (sum & 0xffff)sum = sum + (sum >> 16)#按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1 。~x 类似于 -x-1return (~sum) & 0xffff@property #负责把一个方法变成属性调用def __icmpPacket(self):'''构造 ICMP 报文'''if not self.IPv6:header = struct.pack('bbHHh', 8, 0, 0, self.__id, 0) # TYPE、CODE、CHKSUM、ID、SEQelse:header = struct.pack('BbHHh', 128, 0, 0, self.__id, 0)packet = header + self.__data     # packet without checksumchkSum = self.__inCksum(packet) # make checksumif not self.IPv6:header = struct.pack('bbHHh', 8, 0, chkSum, self.__id, 0)else:header = struct.pack('BbHHh', 128, 0, chkSum, self.__id, 0)return header + self.__data   # packet *with* checksumdef isUnIP(self, IP):'''判断IP是否是一个合法的单播地址'''IP = [int(x) for x in IP.split('.') if x.isdigit()]if len(IP) == 4:if (0 < IP[0] < 223 and IP[1] < 256 and IP[2] < 256 and 0 < IP[3] < 255):return Truereturn Falsedef mPing(self, ip):'''利用ICMP报文探测网络主机存活参数:ipPool  -- 可迭代的IP地址池'''Sock = self.__icmpSocketSock.settimeout(self.timeout)packet = self.__icmpPacketrecvFroms = ''sendThr = SendPing(ip, packet, Sock, self.timeout)sendThr.run()try:recvFroms = Sock.recvfrom(1024)[1][0]except Exception:passreturn recvFromsdef NetworkScan(self, network):  #设置要扫描的网段self.print_logs(" 等待中。。。。。。")if self.isUnIP(network):alive_ip = self.mPing(network)if alive_ip != '':self.print_logs("%s is alive." % network)else:self.print_logs("%s is die." % network)else:self.print_logs("输入的IP地址有误!")def print_logs(self, msg):print(time.strftime("[%Y-%m-%d %H:%M:%S] ", time.localtime()) + msg)logging.info(msg)self._LOGS.put(time.strftime("[%Y-%m-%d %H:%M:%S] ", time.localtime()) + msg)

主要看一下这个语句 header = struct.pack(‘bbHHh’, 8, 0, 0, self.__id, 0) 其中bbHHh是用来控制网络传输的数据格式,其与的参数分别对应ICMP报文的TYPE、CODE、CHKSUM、ID、SEQ字段。然后使用packet = header + self.__data,自定义完整的ICMP数据包,再使用 self.__inCksum(packet)语句,调用计算检验和的方法计算出检验后和,最后在重新封装ICMP数据包。

3.多线程实现网段探测主机存活数。

SendPingThr类中主要构建了run()方法用来实现发送ICMP数据包和建立多线程扫描提高代码运行速度。

class SendPingThr(threading.Thread):def __init__(self, ipPool, icmpPacket, icmpSocket, timeout=3):threading.Thread.__init__(self)self.Sock = icmpSocketself.ipPool = ipPoolself.packet = icmpPacketself.timeout = timeoutself.Sock.settimeout( timeout + 3 )def run(self):time.sleep(0.01)  #等待接收线程启动for ip in self.ipPool:try:self.Sock.sendto(self.packet, (ip, 0))except socket.timeout:breaktime.sleep(self.timeout)

NetworkScan类中添加了makeIpPool()方法生成地址池,修改了NetworkScan()方法用来实现扫描过程,首先它有设置网段的功能,然后通过设置的网段调用makeIpPool()方法生成地址池,再将地址池中的每个地址通过for循环传给mPing()方法实现发送数据包的过程。

 def makeIpPool(self, startIP, lastIP):'''生产 IP 地址池'''IPver = 6 if self.IPv6 else 4intIP = lambda ip: IPy.IP(ip).int()  #将IP地址转换为整型格式ipPool = {IPy.intToIp(ip, IPver) for ip in range(intIP(startIP), intIP(lastIP)+1)}return {ip for ip in ipPool if self.isUnIP(ip)}def NetworkScan(self, network):  #设置要扫描的网段args = "".join(network)ip_prefix = '.'.join(args.split('.')[:-1])ip_start = ip_prefix + ".1"ip_end = ip_prefix + ".255"self.print_logs(" [*] 开始内网主机扫描")ipPool = self.makeIpPool(ip_start, ip_end)alive_ip = self.mPing(ipPool)for i in alive_ip:self.print_logs(" [+] %s is alive." % i)

3.命令行化。

实现命令行化主要使用argparse模块,想具体学习argparse模块的可以查看官方文档。

import networkscans
import networkscan
import argparse
from readlogs import read
if __name__ == '__main__':#定义一个容器parser = argparse.ArgumentParser(description="这是一个探测工具!", formatter_class=argparse.RawTextHelpFormatter,epilog='''use examples:python main.py -i 192.168.1.1  python main.py -s 192.168.1.0python main.py -r logs''')#设置需要的参数parser.add_argument('-i', metavar = '', help = '探测主机存活,-i参数后面输入主机IP')parser.add_argument('-s', metavar = '',help = '探测内网存活的主机,-s参数后面输入一个内网网段')parser.add_argument('-r', metavar='',help = '-r参数后加logs,查看日志文件')args = parser.parse_args()#实现参数的各个功能if args.i:s = networkscan.NetworkScan()s.NetworkScan(args.i)elif args.s:s = networkscans.NetworkScan()s.NetworkScan(args.s)elif args.r:read(args.r)else:print("输入的参数有误,请使用-h参数查看帮助信息!")

四.运行代码。

打包main.py成exe文件,具体方法参考我的这篇博客将自己的python代码打包成exe的可执行文件,然后将生成的exe文件放入C:\Windows\System32打开命令行是用如下图。

main -h 显示帮助信息

main -s 172.22.188.0 探测网段主机

main -i 172.22.188.25 探测具体主机状态

main -r logs 打印日志文件

利用ICMP协议,使用python原始套接字实现主机存活探测工具相关推荐

  1. ICMP拒绝服务攻击(原始套接字系列四)

    拒绝服务攻击(DoS)企图通过使被攻击的计算机资源消耗殆尽从而不能再提供服务,拒绝服务攻击是最容易实施的攻击行为.中美黑客大战中的中国黑客一般对美进行的就是拒绝服务攻击,其技术手段大多不够高明. IC ...

  2. 原始套接字(各种协议的分析)

    原始套接字 1.原始套接字的概述 什么是原始套接字(SOCK_RAW) 数据包在各个层之间的传输 一.(MAC) 链路层封包格式 二.(IP数据报) 网络层.传输层封包格式 2.1.UDP封包格式 2 ...

  3. 网络编程——原始套接字实现原理

    目录 1. 基础知识 1.1.概述 1.2.链路层原始套接字 1.3.网络层原始套接字 2.原始套接字的实现 2.1  原始套接字报文收发流程 2.2链路层原始套接字的实现 2.2.1  套接字创建 ...

  4. 【LWIP】原始套接字(SOCK_RAW)

    收录于: [LWIP]LWIP协议|相关知识汇总|LWIP学习笔记 通常情况下我们接触到的套接字为两类: (1)字节流套接字(SOCK_STREAM):面向连接的socket套接字,用于TCP服务应用 ...

  5. 原始套接字发送IP数据报

    IP是TCP/IP协议族中的核心协议.所有TCP.UDP.ICMP和IGMP数据都通过IP数据报传输.IP提供了一种尽力而为.无连接的数据报交付服务. IPv4头部 图1 IPv4头部 图1显示了IP ...

  6. 原始套接字-SOCK_RAW

    原始套接字 简介 套接口最常用的两种类型:SOCK_STREAM和SOCK_DGRAM. SOCK_STREAM: 流式套接口,传输的是字节流,每次传输的数据没有边界,它是面向连接的,底层使用TCP协 ...

  7. 《Python黑帽子:黑客与渗透测试编程之道》读书笔记(二):原始套接字和流量嗅探

    目录 前言 1.Windows和Linux上的包嗅探 2.解码IP层 3.解码ICMP层 4.发现主机 结语 前言 <Python黑帽子:黑客与渗透测试编程之道>的读书笔记,会包括书中源码 ...

  8. 黑帽python第二版(Black Hat Python 2nd Edition)读书笔记 之 第三章 网络工程-原始套接字与嗅探(1)主机发现工具与包嗅探

    黑帽python第二版(Black Hat Python 2nd Edition)读书笔记 之 第三章 网络工程-原始套接字与嗅探(1)主机发现工具 文章目录 黑帽python第二版(Black Ha ...

  9. c语言实现icmp协议ping命令,利用ICMP协议实现ping命令

    一.实现原理 ping利用ICMP协议包来侦测另一个主机是否可达.Ping的原理是使用了类型码为8的ICMP回送请求包,收到请求的主机则用类型码为0的ICMP回应报文.如果应答包和请求包的标示号.序号 ...

最新文章

  1. 提高PHP编程效率的53个要点(转)
  2. http提交json格式数据自动加\
  3. C语言实现常用数据结构——队列
  4. 听歌是怀念过去 - 潇洒的走
  5. 休眠事实:始终检查Criteria API SQL查询
  6. java criteria exist_Java Criteria.addExists方法代碼示例
  7. Java 自定义排序 Comparator
  8. 织梦服务器系统win10,WIN服务器爆破DEDECMS后台目录
  9. php 递归栏目名叠加,thinkPHP实现递归循环栏目并按照树形结构无限极输出的方法,thinkphp递归...
  10. .Net Core 开发成长路线图
  11. C语言函数多个返回值
  12. ACM学习历程—HDU5666 Segment(数论)
  13. 苹果台式电脑怎么使用计算机,苹果笔记本键盘怎么用_苹果笔记本电脑键盘的使用方法-win7之家...
  14. postman接口导入
  15. Linux常用命令:系统信息、关机重启、文件和目录操作、文件搜索、文件权限、挂载、磁盘空间、用户及群组、解压缩、备份等操作。
  16. chorme唤起Java开发的本地程序全采坑记
  17. 流程图 选择结构 (一)
  18. excel服务器okr系统,OKR工具能帮企业落地OKR吗?从飞书OKR看专业工具的价值
  19. Nodejs正则表达式
  20. java 生产者消费者模式_聊聊并发(十)生产者消费者模式

热门文章

  1. OSPF基础工作过程
  2. 微软2013校园招聘笔试题
  3. 分形几何python代码_Python教程之绘制Mandelbrot集合
  4. Java修仙道路_JAVA修仙??
  5. 蓝桥杯练习 分解质因数
  6. OpenCV 计算运行时间(us,ms,s)
  7. 贪吃蛇小游戏(代码)
  8. YUV 422 10bit
  9. javascript 区号与城市的对应信息
  10. bzoj 4605 崂山白花蛇草水