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还能得到响应报文的问题。

最后给出一个由ScapyIP()类编写的超级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()

参考链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: