Python3网络学习案例二:traceroute详解
1. 写在前面
本文是基于上一篇“ping详解”写的;
不同操作系统下的命令也不同,本文仅针对windows系统,命令为“tracert xxx”,效果如下

2. 使用traceroute做什么
与上一篇ping相似,原理上都是通过向目的主机发送一条消息并通过回显应答来判断目的主机状态。不同的是,traceroute主要用于遍历由源主机到目的主机交互线路上所有的路由器并判断其状态,一般以30为最大TTL(time to live,生存时间),即以该线路上有不超过29(30-1)台路由器为前提进行遍历。
3. 编写traceroute要比编写ping多知道些什么
(1)traceroute基本原理
当路由器收到一份IP数据报,如果该报文的TTL字段是1,则意味着它在网路中的生存周期已经消耗殆尽,本路由处理后还未到达目的主机的话,需要将该数据报丢弃,并给信源主机发送一份ICMP超时报文(包含该中间路由器的地址),这意味着:通过发送一份TTL字段为n的IP数据报给目的主机,就得到了该路径中的第n个路由器的IP地址,那么我们使IP数据报的TTL字段值从1开始依次递增,就可以获得所有中间路由的ip地址。当IP数据报到达目的主机时,由于已经到达目的主机,因此不会再发送ICMP超时报文了,而是ICMP应答报文,通过区分收到的ICMP报文是超时报文(type=11)还是应答报文(type=0),以判断程序应该何时结束;
(2)运行traceroute时要关闭防火墙(直接由系统发起的则不用,CMD)
(至于为什么暂时讲不明白,待续);
(3)TTL
这里的TTL值可自定义,每经过一个节点(路由器等)TTL就会减少1,等到减少到0的时候我们称TTL过期了,此时会返回一条消息给源主机反映这条数据所到达的最后一台路由器的状态/信息;
上面写最大TTL为30,事实上,我们是将数据的TTL从1开始设置的,依次增加1,这样我们就能够依次访问距离源主机一个节点、两个节点、三个节点……的路由器了(即遍历这条路上所有的主机/路由器);
(4)TIMEOUT
超时时间,事实上我们在ping上也提到了,这里在叙述一下,我们在发送了消息之后直到收到回复之后才会返回下一条消息,但是如果消息刚发出去就丢失了,或者其他情况导致没有消息返回呢?我们不可能无节制地等下去,因此,我们设置一个TIMEMOUT并规定,如果响应时间超过了这个长度我们就认为这一次的交流失败了,并向源主机(或者使用者)报告“请求超时”;
(5)根据返回消息的type和code来判断该主机(路由器)状态
编写ping的时候我们只需要指导回显请求(type = 8,code = 0)和回显应答(type = 0, code = 0)就行了,但是对于traceroute,我们还要注意到其他几个不同的type:
type 11,code = 0:ttl过期,换句话说就是当数据经过某一个路由器时ttl减少为零了,此时接收到的返回信息反映了这个路由器的状态(这个路由器的ip等);
type 3:出错了,所对应的code有数个值,但是我们只要知道返回消息向我们反映说这一次的路线不能正确地得到路由器的状态就行了,详细信息可以看下图中的type = 3时code对应的状态

(6)tries
我们上面提到,消息再发送出去的时候可能会丢失或者其他意外情况出现,但是我们并不希望一次失败就认为这个路由器不可访问等,因此,我们基本上会对这条线路尝试多次,一般为三次
(7)DNS
发送和接收消息都很快(以ms为单位),但是通过DNS查询返回消息的主机信息就显得有些慢了(以s为单位),因此,我们通常在尝试发送三次消息后再通过DNS查看是哪个主机响应了
4. 源码
# --coding:utf-8--
import socket
import os
import sys
import struct
import time
import select
from pip._vendor.distlib.compat import raw_input
# ICMP 回送请求报文
TYPE_ECHO_REQUEST = 8
CODE_ECHO_REQUEST_DEFAULT = 0
# ICMP 回送应答报文
TYPE_ECHO_REPLY = 0
CODE_ECHO_REPLY_DEFAULT = 0
# ICMP 数据报超时报文
TYPE_ICMP_OVERTIME = 11
CODE_TTL_OVERTIME = 0;
# ICMP 目的站不可达报文
TYPE_ICMP_UNREACHED = 3
MAX_HOPS = 30 # 设置路由转发最大跳数为30
TIMEOUT = 3 # 如果一个请求超过3s未得到响应,则被认定为超时
TRIES = 3 # 对于每个中间站点,探测的次数设置为1
# 校验和
def check_sum(str):
csum = 0
countTo = (len(str) / 2) * 2
count = 0
while count < countTo:
thisVal = str[count + 1] * 256 + str[count]
csum = csum + thisVal
csum = csum & 0xffffffff
count = count + 2
if countTo < len(str):
csum = csum + str[len(str) - 1]
csum = csum & 0xffffffff
csum = (csum >> 16) + (csum & 0xffff)
csum = csum + (csum >> 16)
answer = ~csum
answer = answer & 0xffff
answer = answer >> 8 | (answer << 8 & 0xff00)
return answer
# 构建ICMP报文
def build_packet():
# 初始检验和
my_checksum = 0
# 进程号作标识
my_id = os.getpid() & 0xffff
# 序列号
my_seq = 1
# 打包出二进制首部
my_header = struct.pack("bbHHh", TYPE_ECHO_REQUEST, CODE_ECHO_REQUEST_DEFAULT, my_checksum, my_id, my_seq)
# 以当前系统时间作为报文的数据部分
my_data = struct.pack("d", time.time())
# 构建临时的数据报
package = my_header + my_data
# 利用原始数据报来计算真正的校验和
my_checksum = check_sum(package)
# 处理校验和的字节序列类型:主机序转换为网络序
if sys.platform == 'darwin':
my_checksum = socket.htons(my_checksum) & 0xffff
else:
my_checksum = socket.htons(my_checksum)
# 重新构建出真正的数据包
my_header = struct.pack("bbHHh", TYPE_ECHO_REQUEST, CODE_ECHO_REQUEST_DEFAULT, my_checksum, my_id, 1)
ip_package = my_header + my_data
return ip_package
def main(hostname):
print("routing {0}[{1}]".format(hostname, socket.gethostbyname(hostname)))
for ttl in range(1, MAX_HOPS):
print("%2d" % ttl, end="")
for tries in range(0, TRIES):
# 创建icmp原始套接字
icmp_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
icmp_socket.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, struct.pack('I', ttl))
icmp_socket.settimeout(TIMEOUT)
# 构建报文并发送
icmp_package = build_packet()
try:
icmp_socket.sendto(icmp_package, (hostname, 0))
except socket.gaierror as e:
print("Wrong!not a effective ip address!")
return
# 进入阻塞态,等待接收ICMP超时报文/应答报文
start_time = time.time()
select.select([icmp_socket], [], [], TIMEOUT)
end_time = time.time()
# 计算阻塞的时间
during_time = end_time - start_time
if during_time >= TIMEOUT:
print(" * ", end="")
if tries >= TRIES - 1:
print(" request timeout")
continue
elif during_time == 0:
print(" * ", end="")
else:
print(" %4.0f ms " % (during_time * 1000), end="")
if tries >= TRIES - 1:
ip_package, ip_info = icmp_socket.recvfrom(1024)
# 从IP数据报中取出ICMP报文的首部,位置在20:28,因为IP数据报首部长度为20
icmp_header = ip_package[20:28]
# 解析ICMP数据报首部各字段
after_type, after_code, after_checksum, after_id, after_sequence = struct.unpack("bbHHh", icmp_header)
if after_type == TYPE_ICMP_UNREACHED: # 目的不可达
print("Wrong!unreached net/host/port!")
break
elif after_type == TYPE_ICMP_OVERTIME: # 超时报文
print(" %s" % ip_info[0])
continue
elif after_type == 0: # 应答报文
print(" %s" % ip_info[0])
print("program run over!")
return
else:
print("request timeout")
print("program run wrongly!")
return
if __name__ == "__main__":
ip = raw_input("please input a ip address:")
main(ip)
Python3网络学习案例二:traceroute详解的更多相关文章
- Struts2学习笔记二 配置详解
Struts2执行流程 1.简单执行流程,如下所示: 在浏览器输入请求地址,首先会被过滤器处理,然后查找主配置文件,然后根据地址栏中输入的/hello去每个package中查找为/hello的name ...
- vue.js学习笔记(二)——vue-router详解
vue-router详解 原文链接:www.jianshu.com 一.前言 要学习vue-router就要先知道这里的路由是什么?为什么我们不能像原来一样直接用<a></a> ...
- Struts2学习笔记(二)——配置详解
1.Struts2配置文件加载顺序: default.properties(默认常量配置) struts-default.xml(默认配置文件,主要配置bean和拦截器) struts-plugin. ...
- Python3网络学习案例一:Ping详解
1. 使用Ping做什么 ping用于确定本地主机是否能与另一台主机成功交换(发送与接收)数据包,再根据返回的信息,就可以推断TCP/IP参数是否设置正确,以及运行是否正常.网络是否通畅等. 2. 效 ...
- Python3网络学习案例三:编写web server
1. 写在前面 这里总结的并不够详细,有时间了再进行补充. 2. 设计思路 HTTP协议是建立在TCP上的1. 建立服务器端TCP套接字(绑定ip,port),等待监听连接:listen(2. 打开浏 ...
- [C#] 类型学习笔记二:详解对象之间的比较
继上一篇对象类型后,这里我们一起探讨相等的判定. 相等判断有关的4个方法 CLR中,和相等有关系的方法有这么4种: (1) 最常见的 == 运算符 (2) Object的静态方法ReferenceEq ...
- SpringMVC学习总结(二)——DispatcherServlet详解
摘要: DispatcherServlet是前端控制器设计模式的实现,提供Spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得Spring ...
- Vue学习系列(二)——组件详解
前言 在上一篇初识Vue核心中,我们已经熟悉了vue的两大核心,理解了Vue的构建方式,通过基本的指令控制DOM,实现提高应用开发效率和可维护性.而这一篇呢,将对Vue视图组件的核心概念进行详细说明. ...
- Python3网络学习案例四:编写Web Proxy
代理服务器的定义和作用请走百度百科~ 1. Web Proxy的实现思路 这是基于上一篇"编写Web Server"写的,主要逻辑见下图: 我们要写的就是中间的Web Proxy部 ...
随机推荐
- mysql-2-where
#进阶2:条件查询 /* 语法: SELECT 查询列表 FROM 表名 WHERE 筛选条件 分类: 1.按条件表达式筛选:> < = != <> >= <= 2 ...
- Android 自定义Vie 对勾CheckBox
天在美团点外卖,有一个商品推荐的条目,上面的CheckBox是自定义的,虽然我们大部分都是用图片来自定义样式.但是还是可以自定义View来绘制的,只要画一个圆和对勾即可. 最终效果 最终效果.png ...
- Hadoop框架:NameNode工作机制详解
本文源码:GitHub·点这里 || GitEE·点这里 一.存储机制 1.基础描述 NameNode运行时元数据需要存放在内存中,同时在磁盘中备份元数据的fsImage,当元数据有更新或者添加元数据 ...
- day15 Pyhton学习
迭代器 掌握for循环 实际上for循环的本质,就是将一个可迭代的变成迭代器 每一次从中取值都相当于执行了一次next 如果是迭代器,那么只能取一次值 生成器 - 本质就是迭代器 生成器函数(返回值是 ...
- ip地址和网络端口总结
ip地址 ip地址默认指ipv4地址,用4个字节表示,转换为点分10进制,可以表达范围0.0.0.0到255.255.255.255的地址,大约为42.95亿个地址.互联网编号分配机构(IANA,In ...
- C 和 C++ 打起来了!曾今最亲密的伙伴到现今的不爽?
70年代初,贝尔实验室创建了C语言,它是开发UNIX的副产品.很快C就成为了最受欢迎的编程语言之一.但是对于Bjarne Stroustrup来说,C的表达能力还不够.于是,他在1983年的博士论文中 ...
- 网络编程—【自己动手】用C语言写一个基于服务器和客户端(TCP)!
如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西--socket(套接字). socket(套接字):简单来讲,socket就是用于描述IP地 ...
- 【状态压缩DP】HDU 4352 XHXJ'S LIS
题目大意 Vjudge链接 定义一个数的内部LIS长度表示这个数每个数位构成的序列的LIS长度,给出区间\([l,r]\),求区间内内部LIS长度为\(k\)的数的个数. 输入格式 第一行给出数据组数 ...
- 微信小程序-基于高德地图API实现天气组件(动态效果)
微信小程序-基于高德地图API实现天气组件(动态效果) 在社区翻腾了许久,没有找到合适的天气插件.迫不得已,只好借鉴互联网上的web项目,手动迁移到小程序中使用.现在分享到互联网社区中,帮助后续有 ...
- java默认值
注意:此处默认值是在类成员时才可以被初始化有默认值 如果时在局部变量中,必须先自己初始化才可以使用,否则编译失败