Python网络编程2--实现Ping程序与Traceroute程序

一、ICMP报文格式

   ICMP全名为(INTERNET CONTROL MESSAGE PROTOCOL)网络控制消息协议。ICMP 大致分成两种功能:差错通知和信息查询。ICMP 的内容是放在IP 数据包的数据部分里来互相交流的。也就是,从ICMP的报文格式来说,ICMP 是IP 的上层协议。

  • 报文格式
    ICMP报文格式
  • 查询报文格式
    ICMP查询报文
  • 查询报文字段
    ICMP查询报文字段
  • ICMP消息列表
    ICMP消息列表

二、实现Ping程序

2.1 Python Struct模块介绍

  • 用处
    • 按照指定格式将Python数据转换为字符串,该字符串为字节流,如网络传输时,不能 + 传输int,此时先将int转化为字节流,然后再发送;
    • 按照指定格式将字节流转换为Python指定的数据类型;
    • 处理二进制数据,如果用struct来处理文件的话,需要用’wb’,’rb’以二进制(字节流)写,读的方式来处理文件;
  • 内置函数
    • struct.pack(fmt,v1,v2,…)
      按照给定的格式(fmt),把数据转换成字符串(字节流),并将该字符串返回.
    • struct.unpack(format, buffer)
      从buffer中按照format解析对象
  • 举例
  • 将int转换为bytes
    将256这个数字通过pack函数转化成了bytes类型,其中pack的第一个参数i表示buf1为int类型的数据。
buf1 = 256
bin_buf1 = struct.pack('i', buf1)  # 'i'代表'integer'
ret1 = struct.unpack('i', bin_buf1)
print(bin_buf1, '  <====>  ', ret1)
  • 将浮点数转化为bytes
buf2 = 3.1415
bin_buf2 = struct.pack('d', buf2)  # 'd'代表'double'
ret2 = struct.unpack('d', bin_buf2)
print(bin_buf2, '  <====>  ', ret2)
  • 将字符串转换为bytes
    11s表示buf3为长度11个字符的数组。同理,也可以使用“11i”表示11个int类型的数据,还可以使用“iiiiiiiiiii”表示11i
buf3 = 'Hello World'
bin_buf3 = struct.pack('11s', bytes(buf3,encoding='ascii'))  # '11s'代表长度为11的'string'字符数组
ret3 = struct.unpack('11s', bin_buf3)
print(bin_buf3, '  <====>  ', ret3)

2.2 Python具体实现

  • 具体实现脚本
#!/usr/bin/python3.4
# -*- coding=utf-8 -*-

from kamene.all import *
import time,sys,random,re,struct


def ping_one(dst,id_no,seq_no,ttl_no):
    '''定义ICMP Echo request数据包'''
    send_time = time.time() #获取发包时间
    time_in_bytes = struct.pack('>d',send_time)#把时间转为2进制,'>'为网络字节序,d为8个字节浮点/
    ping_one_reply = sr1(IP(dst=dst, ttl=ttl_no)/ICMP(id=id_no,seq=seq_no)/time_in_bytes, timeout = 1, verbose=False)
    #产生一个ICMP echo request数据包,scapy默认类型为echo request,数据部分为2进制编码后的时间
    try:
        if ping_one_reply.getlayer(ICMP).type == 0 and ping_one_reply.getlayer(ICMP).code == 0 and ping_one_reply.getlayer(ICMP).id == id_no:
        #收到的数据包,要为echo reply(type为0,code为0),并且id位还要匹配。
            reply_source_ip = ping_one_reply.getlayer(IP).src
            #提取echo reply的源IP地址
            reply_seq = ping_one_reply.getlayer(ICMP).seq
            #提取echo reply的序列号
            reply_ttl = ping_one_reply.getlayer(IP).ttl
            #提取echo reply的TTL
            reply_data_length = len(ping_one_reply.getlayer(Raw).load) + len(ping_one_reply.getlayer(Padding).load) + 8
            #数据长度等于 数据长度(Raw) + 垫片长度(Padding) + 8字节(ICMP头部长度)
            reply_data = ping_one_reply.getlayer(Raw).load
            #提取数据(Raw)
            receive_time = time.time()
            #计算接收的时间
            echo_request_sendtime = struct.unpack('>d',reply_data)
            #把二进制的数据转换为发送时间
            time_to_pass_ms = (receive_time-echo_request_sendtime[0]) * 1000
            #(接收时间 - 发送时间) * 1000为毫秒数为消耗时间的毫秒数
            return reply_data_length, reply_source_ip, reply_seq, reply_ttl, time_to_pass_ms
            #返回echo reply中的数据总长度,源IP,序列号,TTL,和消耗的时间
    except Exception as e:
        if re.match('.*NoneType.*',str(e)):
            #如果没有回应,就返回None
            return None

def my_ping(dst):
    '''发送ICMP Echo request,并格式化输出结果'''
    id_no = random.randint(1,65535)#随机产生ID号
    for i in range(1,6): #ping 五个包
        ping_result = ping_one(dst,id_no,i,64)
        if ping_result:#把返回的值打印出来
            print('%d bytes from %s: icmp_seq=%d ttl=%d time=%4.2f ms' % (ping_result[0], ping_result[1], ping_result[2], ping_result[3], ping_result[4]))
        else:
            print('.',end='',flush=True)#如果没有回应就打印'.',注意flush选项,默认会缓存并不直接打印
        time.sleep(1)#间隔一秒发送数据包

if __name__ == '__main__':
    ip=input("ip>>>")
    my_ping(ip)
  • 执行效果
    使用Pycharm运行结果如下


    Pycharm执行效果

    在linux接口下使用Tcpdump抓包如下,可以看到主机向外发送大量ICMP Request包,并收到Reply包。


    image.png

三、实现Traceroute程序

3.1 Traceroute原理

   Traceroute程序发送IP数据包给目标主机,其中第一个数据包中TTL值为1,当中间设备收到这个数据包时将TTL减1,此时TTL值变成0,会向源主机发送一份ICMP"超时"信息,Traceroute程序收到信息后发送TTL值加1的数据包,以此类推,直到目标主机收到数据包,由于数据包内包含的UDP端口号(大于3000,端口号没发送一个数据包后加1)为一个不可能值,因此目标主机将发送一份"端口不可达”的ICMP差错报文给源主机,Tracertroute程序收到端口不可达的ICMP差错报文后,则判定已到达目标不在发送报文。

3.2 实验环境

   通过Ensp搭建一个模拟网路环境,执行脚本的Linux主机桥接到网络中。


Traceroute实验环境

3.3 Python实现

  • 具体实现脚本
#!/usr/bin/python3.4
# -*- coding=utf-8 -*-

from kamene.all import *
import time
import re

def Tracert_one(dst,dport,ttl_no):
    '''发一个Traceroute包,参数需要目的地址,目的端口,TTL'''
    send_time = time.time()#记录发送时间
    Tracert_one_reply = sr1(IP(dst=dst, ttl=ttl_no)/UDP(sport=6600,dport=dport)/b'my traceroute!!!', timeout = 1, verbose=False)
    #Scapy中UDP默认源目端口53,需要将源端口也改掉,否则中间设备将不回应
    try:
        if Tracert_one_reply.getlayer(ICMP).type == 11 and Tracert_one_reply.getlayer(ICMP).code == 0:
            #如果收到TTL超时
            hop_ip = Tracert_one_reply.getlayer(IP).src
            received_time = time.time()
            time_to_passed = (received_time - send_time) * 1000
            return 1, hop_ip, time_to_passed #返回1表示并未抵达目的地
        elif Tracert_one_reply.getlayer(ICMP).type == 3 and Tracert_one_reply.getlayer(ICMP).code == 3:
            #如果收到端口不可达
            hop_ip = Tracert_one_reply.getlayer(IP).src
            received_time = time.time()
            time_to_passed = (received_time - send_time) * 1000
            return 2, hop_ip, time_to_passed #返回2表示抵达目的地
    except Exception as e:
        if re.match('.*NoneType.*',str(e)):
            return None #测试失败返回None,没有回包

def MY_Tracert(dst,hops):
    dport = 33434 #Traceroute的目的端口从33434开始计算
    hop = 0
    while hop < hops:
        dport = dport + hop
        hop += 1
        Result = Tracert_one(dst,dport,hop)
        if Result == None:#如果测试失败就打印‘*’
            print(str(hop) + ' *',flush=True)
        elif Result[0] == 1:#如果未抵达目的,就打印这一跳和消耗的时间
            time_to_pass_result = '%4.2f' % Result[2]
            print(str(hop) + ' ' + str(Result[1]) + ' ' + time_to_pass_result + 'ms')
        elif Result[0] == 2:#如果抵达目的,就打印这一跳和消耗的时间,并且跳出循环!
            time_to_pass_result = '%4.2f' % Result[2]
            print(str(hop) + ' ' + str(Result[1]) + ' ' + time_to_pass_result + 'ms')
            break
        time.sleep(1)

if __name__ == '__main__':
    conf.route.add(net='172.16.10.0/24',gw='192.168.10.115')#为Scapy添加路由
    destIP=input("目标IP>>>")
    hops=input("最大跳数>>>")
    MY_Tracert(destIP, int(hops))
  • 执行效果
    通过Pycharm执行脚本


    执行效果
  • 在路由器入接口上抓包
    R1收到TTL为1的UDP数据包,R1回应ICPM(TTL超时)


    TTL 1 UDP报文

    ICMP TTL超时

    在次发送TTL值为2 的UDP数据包,到达目的R2,发送ICPM(端口不可达)


    TTL 2 UDP报文
ICMP 端口不可达

参考:(https://www.cnblogs.com/iiiiher/p/8513748.html)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容