Scrapy IP()类 编程指南(基础)
IP简介
工欲善其事,必先利其器,在聊Scapy
IP
类时,我们先要了解IP
是什么。
IP
指的是Internet Protocol(互联网协议)的数据包。Internet Protocol是互联网上用于在网络中传输数据的一种协议。在TCP/IP
协议族中,IP
层负责数据包的路由和寻址,确保数据能够在网络中正确传递。
IP
协议定义了一种在网络中唯一标识设备(主机或路由器)的方式,并提供了一种将数据分割成小的数据包进行传输的机制。每个数据包都包含源和目标设备的IP
地址,以便路由器能够正确地将数据包从源传输到目标。
IP
协议又分为IPv4
、IPv6
,两个版本,IPv6
可以理解为IPv4
的升级版,但它们又有所不同,它的出现,是为了解决IPv4
地址即将耗尽的问题,日后也会详细说明IPv6
,今天我们主要的对象就是IPv4
。
在IPv4协议中,IP地址主要被分为五个类别,通常称为IP地址的分类。这些分类是基于地址的网络部分的位数,以及主机部分的位数。这五个主要的IP地址分类是:A类、B类、C类、D类和E类。
- 类A地址:
- 范围:1.0.0.0 到 126.255.255.255
- 特点:第一个字节(8位)用于网络部分,剩余的三个字节(24位)用于主机部分。
- 可用网络:2^7 - 2 = 126,因为0和127保留作为特殊用途。
- 类B地址:
- 范围:128.0.0.0 到 191.255.255.255
- 特点:前两个字节(16位)用于网络部分,后两个字节(16位)用于主机部分。
- 可用网络:2^14 - 2 = 16,382
- 类C地址:
- 范围:192.0.0.0 到 223.255.255.255
- 特点:前三个字节(24位)用于网络部分,最后一个字节(8位)用于主机部分。
- 可用网络:2^21 - 2 = 2,097,150
- 类D地址:
- 范围:224.0.0.0 到 239.255.255.255
- 特点:用于多播(Multicast)通信,不分配给单个主机或网络。
- 类E地址:
- 范围:240.0.0.0 到 255.255.255.255
- 特点:保留作为将来使用的实验和开发。
IP报文头
在Scapy中,IP报文头与IP()
类直接相关。IP()
类用于创建和处理IPv4报文头,它是Scapy中用于构建IPv4数据包的类。使用IP()
类,我们可以轻松地定义IPv4数据包的各种属性,如源地址、目标地址、协议类型等。
版本(Version):
- 占4位。
- 指定IP协议的版本,IPv4的版本号为4。
头部长度(IHL - Internet Header Length):
- 占4位。
- 指定IPv4头部的长度,以32位字(4字节)为单位。由于IPv4头部中最少有20字节,因此该字段的值至少为5(表示20字节)。
服务类型(Type of Service - TOS):
- 占8位。
- 用于指定服务质量,包括优先级、延迟、吞吐量和可靠性。
总长度(Total Length):
- 占16位。
- 指定整个IPv4数据包的长度,包括头部和数据。最大长度为65,535字节。
标识(Identification):
- 占16位。
- 用于将相关的数据包片段组合成完整的数据包。
标志位(Flags):
- 占3位。
- 包含“不分片(Don’t Fragment)”和“更多片段(More Fragments)”标志。
片偏移(Fragment Offset):
- 占13位。
- 指定数据包片段在原始数据包中的偏移量。
生存时间(Time to Live - TTL):
- 占8位。
- 限制数据包在网络中的生存时间,每经过一个路由器,该字段值减一。当TTL为0时,数据包被丢弃。
协议(Protocol):
- 占8位。
- 指定上层协议,例如TCP(6)、UDP(17)、ICMP(1)等。
头部校验和(Header Checksum):
占16位。
用于检测IPv4头部的错误,主要是检测在传输过程中头部信息是否损坏。
源地址(Source Address):
占32位。
指定数据包的源IP地址。
目标地址(Destination Address):
占32位。
指定数据包的目标IP地址。
选项(Options):
可选字段,用于提供一些额外的信息。通常很少被使用,因为IPv4头部本身已经包含了足够的信息。
Scapy IP()使用
而在Scapy中,IP报文头与IP()
类直接相关。IP()
类用于创建和处理IPv4报文头,它是Scapy中用于构建IPv4数据包的类。
以下是IP()
类的一些常用属性:
- src:指定源IP地址。
#两种方式指定
IP(src="x.x.x.x")#构造函数指定
ip_packet = IP() #构造空参数
ip_packet.src = "x.x.x.x" #成员属性指定
- dst:指定目标IP地址
#两种方式指定
IP(dst="x.x.x.x")#构造函数指定
ip_packet = IP() #构造空参数
ip_packet.dst = "x.x.x.x" #成员属性指定
- proto:指定上层协议,例如TCP、UDP等。
#两种方式指定
IP(proto="TCP")#构造函数指定
ip_packet = IP() #构造空参数
ip_packet.proto = "TCP" #成员属性指定
- ttl:设置Time-to-Live值。
#两种方式指定
IP(ttl="5")#构造函数指定
ip_packet = IP() #构造空参数
ip_packet.ttl = 5 #成员属性指定
- tos:设置Type of Service值。
#两种方式指定
IP(tos=0b10101010)#构造函数指定
ip_packet = IP() #构造空参数
ip_packet.tos = 0b10101010 #成员属性指定
- ihl:设置IP报文头长度(通常不需要手动设置)。
#两种方式指定
IP(ihl=5)#构造函数指定
ip_packet = IP() #构造空参数
ip_packet.ihl = 20 #成员属性指定
- flags:设置IP标志位。
#两种方式指定
IP(flags="MF")#构造函数指定
ip_packet = IP() #构造空参数
ip_packet.src ="MF"#成员属性指定
- options:设置IP报文头的选项字段。
#两种方式指定
IP(options=[(1, 1, b'\x01')])#构造函数指定
ip_packet = IP() #构造空参数
ip_packet.src = [(1, 1, b'\x01')] #成员属性指定
Scapy IP() 实战使用
IP有这么多头部字节,我们用的最多也就是src
源地址和dst
目的地址,它们决定着,我们的数据包,由谁发起,又要发送到哪里。现在我们使用Scapy
来构造一个IP
头。
from scapy.layers.inet import IP, ICMP
from scapy.sendrecv import sr1
# 创建一个包含IP和ICMP协议的数据包,也就是ping包
ip_packet = IP(dst="192.168.30.55") / ICMP()
# 发送数据包并等待响应
response = sr1(ip_packet)
# 打印响应信息
response.show()
如果我将src源IP地址,修改在发送,再来查看结果
from scapy.layers.inet import IP, ICMP
from scapy.sendrecv import sr1
ip_packet.src = '123.123.222.111'
send_ip = sr1(ip_packet)
这里可能有人就有疑问了,源IP我明明已经修改为123.123.222.111
,为什么还会有响应包?这里就牵扯到交换机的原理以及ARP的知识了。
交换机是一个二层设备,为什么说它是二层设备呢,因为交换机的基础功能就是处理OSI网络模型中的第二层(数据链路层),在数据链路层来说,它更关心是MAC物理地址的交换,它在工作过程中维护一张关键的表格。
MAC地址表:
- 它是一个用于存储设备MAC地址与物理端口对应关系的表格。当交换机收到一个帧(数据包)时,它会查看帧中的目标MAC地址,并将这个MAC地址与接收到帧的端口进行关联,更新MAC地址表。这样,交换机就知道将数据帧发送到哪个端口,以便正确地转发数据。
MAC地址就是设备的独特标签,IP地址是用于在网络中找该设备的逻辑地址,而怎么通过设备的逻辑地址去找到这台设备呢,这就要通过ARP协议来解决了。ARP协议可以帮助你根据一个逻辑地址找到具体设备的物理设备,以便进行直接通信。在主机系统一般都会存在一个ARP表作用如下:
ARP表:
- 它用于存储IP地址与对应的MAC地址之间的映射关系。当设备需要与网络中的其他设备通信时,它首先会查看自己的ARP表。如果在ARP表中找不到目标设备的IP地址对应的MAC地址,设备就会发起ARP请求,请求网络中其他设备告知它目标设备的MAC地址。
因为我们没有构造二层Ether
包,所以这个数据包的mac地址会以默认接口的MAC地址认作缺省值(默认属性)进行发送,当这个包经过交换机时,交换机会读取该数据包的目的MAC信息,发现目的MAC信息全为F就为广播包,他就会向所有端口发送ARP广播报文,询问谁是192.168.30.55,,找到相应的接口进行二层转发,当数据包到达目的后,目标主机会先检查数据包的源MAC地址,并在本机的ARP表中查找对应的MAC与IP绑定关系,完成接收后,目标主机会根据请求包中的源IP、源MAC构造一个响应包发给源主机
这就解释了,为什么我随便设置的Ip还能得到响应报文的问题。
最后给出一个由Scapy
IP()类编写的超级Ping工具:
# coding=utf-8
"""
@Author: 迪奥布斯
@create time : 2024/1/27
@超级ping
"""
import re
import time
from scapy.config import conf
from scapy.layers.inet import IP, ICMP
from scapy.sendrecv import sr, sr1
ips = []
def order_ip(ip):
ip_source, ip_num = ip.rsplit('.', 1)
start, end = ip_num.split('-')
if start > end:
tmp = start
start = end
end = tmp
ip_list = [ip_source + '.' + str(num) for num in range(int(start), int(end))]
return ip_list
def num_ip(ip):
b = ip.split(',')
a = set(b)
return list(a)
def ip_run(ip):
return [ip]
def check_ip_format(ip):
patterns = {
"ipv4": (re.compile(r'^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$'), ip_run),
"ip_range": (re.compile(
r'^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}-(25[1-5]|2[0-4]\d|1\d\d|[1-9]?\d)$'),
order_ip),
"multiple_ips": (re.compile(
r'(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}-(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)|(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})(?:,(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}-(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)|(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))*$'
), num_ip),
}
matched = False
for pattern_type, (pattern, fun) in patterns.items():
if pattern.match(ip):
ips.append(fun(ip))
matched = True
break
if not matched:
print("输入的IP无效,请重新输入!")
exit()
def send_icmp(ip, interface):
# 构建 ICMP 请求包
ping_pkt = IP(dst=ip) / ICMP(id=1000)
try:
# 发送 ICMP 请求并等待响应 n
time.sleep(0.5)
start_time = time.time()
ip_response = sr1(ping_pkt, timeout=1, verbose=False, iface=interface)
end_time = time.time()
if ip_response is not None:
ttl_sys = {255: 'Ulinx系统(交换机、路由器)',
128: 'Windos系统',
64: 'MacOS/Linux'}
for ttl_num, sys_name in ttl_sys.items():
if ip_response[IP].ttl == ttl_num:
sys = sys_name
print(
f'{ip_response[IP].src} ---> 可达,{ip_response[IP].ttl} ---> 跳数,它是{sys},花费 {(end_time - start_time) * 1000:0.1f} 毫秒')
else:
print(f'{ip} ---> 无响应')
except Exception as e:
print(f'发生错误: {e}')
exit()
def start_main(ip, num, interface):
check_ip_format(ip)
if len(ips[0]) < 2:
a = num * len(ips[0])
b = a - num
while not a == b:
send_icmp(ips[0][0], interface)
a -= 1
elif len(ips[0]) >= 2:
a = num * len(ips[0])
for ip_num in ips[0]:
b = a - num
while not a == b:
send_icmp(ip_num, interface)
a -= 1
def tiShi():
print("*" * 80)
print("目前可以实现功能,多IP范围ping主机判断主机系统")
print("IP输入格式为:")
print("单个IP:192.168.0.1")
print("范围IP:192.168.0.1-255")
print("多个IP:192.168.0.1,192.168.0.2")
print("ping的次数,默认为4次")
print("指定网卡接口:不指定为默认网卡")
print("指定接口为网卡名称,例如:windows的网卡名称“以太网”或“以太网1,linux的网卡名”eth0“或”ens0“")
print("*" * 80)
def user_input():
tiShi()
while True:
target_ip = input('请输入你要ping的ip:')
target_num = input('请输入要ping的次数:').strip()
if not target_num or not target_num.isdigit():
ai = 4
print(f"输入为空,设置默认值为: {ai}", end='')
else:
try:
ai = int(target_num)
print(f"转换后的整数值为: {ai}", end='')
except ValueError:
print("输入无效,无法转换为整数。", end='')
print(target_num)
target_interface = input("请输入网卡名称:")
if target_interface == '':
target_interface = conf.iface
print('输入为空为默认网卡:', conf.iface.name)
start_main(target_ip, ai, target_interface)
if __name__ == '__main__':
user_input()