send函数是Scapy中发包的重要函数,使用Scapy的程序员免不了经常与它打交道。但是,我们真的了解它吗?思此有感,我特地翻阅了源码,并将自己的分析写成博客分享给大家。水平不高,如有疑惑请在评论区留言。
文档中是这样介绍send函数的(怀疑机翻):
scapy.sendrecv.send(x, iface=None, **kargs)
在第三层发送数据包
参数
x -- 包裹
inter -- 两个数据包之间的时间(以秒为单位)(默认值为0)
loop -- send packet indefinitely (default 0) (bing翻译为“无限期发送数据包 默认值 0”)
count -- 要发送的数据包数(默认无=1)
verbose -- 详细模式(默认无=conf.详细) (指conf.verb)
realtime -- 在发送下一个包之前,请检查是否已发送了一个包
return_packets -- 返回发送的数据包
socket -- 要使用的套接字(默认为conf.L3socket(kargs))
iface -- 发送数据包的接口
monitor -- (不在linux上)以监视模式发送
返回: 没有 (可能指None)
(源码附后)
绿色背景的是我的注释。
不难看出,send函数的参数列表显式地由三个部分组成:x指要发送的包,iface指发送数据包的接口,还有一个接受不定参数的kargs。
文档中提到的参数和返回值,经研究后,结果如下:
参数解析:
x:
类型为_PacketIterable,无默认值。
指要发送的包,可传递包、迭代器或存有包的列表。不能传元组(原因不明)。send函数的底层使用for循环遍历x参数,按理来说可迭代对象均可传入。
inter:
类型为int或float,默认为0。
发包后的停顿时间,以秒为单位。注释上面标注为int型,实际传入float也可正常运行。这是因为它调用的是time模块中的sleep函数。
loop:
类型为int,默认为0。
发送数据包的数量,是发包while循环的条件。如果没有传递loop的值,同时也没有传递count的值,则loop值会被设为-1。如果传了count的值,则loop会被设为-count。每次循环后,loop值加1,加到0时循环停止。
count:
类型为int,默认为None。
发送包的数量。当count为None时,loop被设为-1,反之被设为-count。注意不要传入非None、int的值,否则报错。如果传入非负数,正常运行。传入小数,while循环因为loop始终不为0而不会停止。传入正数,因为send函数底层的做法是将loop赋为-count,在每次循环且loop小于0时执行loop += 1,所以loop始终不为0,故循环不会终止。
verbose:
类型为int,默认值为conf.verb,即2。
是否开启详细模式。该值为真时,打印发包过程的详细信息(其实也没多详细)。即每法送一个包,打印一个点,并在发包结束之后打印发送的总包数。
realtime:
类型为bool,默认为False。
是否在发送下一个包之前,检查是否已发送了一个包。
这个参数有没有用我持怀疑态度。在捋完那段绕来绕去的源码后,我得出一个等式:
第一次发包时间-第一个包构造时的时间>第二次发包时间-第二个包构造时时间,只要满足这个不等式,send函数就会使用sleep函数阻塞进程第二个包构造时间-第一个包构造时间+第一次发包时间-第二次发包时间。这段代码是如何检查是否发送了一个包的,我不得而知。
return_packets:
类型为bool,默认为False。
是否返回发送的数据包。注意,返回的是一个PacketList对象,不是包。PacketList可以用访问下标的方法访问其中的元素。具体的包储存在res成员中。
socket:
类型为SuperSocket, 默认值为conf.L3socket(kargs))。
使用的套接字。当缺省时,send函数会自行处理。
iface:
类型为str,默认值None。该参数用于指定网卡的名称,网卡的使用由send函数内部完成。如传入"eth0",send函数就会使用eth0网卡发包。当缺省时,send函数会自行选择合适的网卡。
montor:
(用的Linux,没找到这个东西)
返回值解析:
文档中提到的返回值为None。但经源码分析之后,send函数的返回值应有两个:None或PacketList。当return_packets参数为真时,返回PacketList类,反之返回None。
None值不多做解释。
PacketList是定义在plist模块中的一个类。它的一个父类_PacketList重写了__repr__(str类进行类型转换和print函数调用的方法)、__iter__(迭代开始时调用的方法)、__getitem__(索引操作使用的方法)、__add__(重载加法运算符)、__len__(len函数调用的方法)。由此可看出Scapy中对运算符、语句和内置函数的运用不少。
故此可实现以下操作:
1.使用print函数打印或调用str类转换send函数的返回值。
output:
<PacketList: TCP:0 UDP:0 ICMP:0 Other:1>
2.像迭代range函数的返回值一样迭代send函数的返回值。
3.使用索引调取储存在send函数返回值的数据包。这并不是对其中res成员的简单访问,它还涉及其他返回值,目前还没有研究。
4.使用加法运算符在send函数的返回值之间运算。使用这个东西需要注意。如下:
代码:
a = b = send(IP(), return_packets=True)
d = a + b
print(d)
output:
. (这里有个点)
Sent 1 packets.
<PacketList+PacketList: TCP:0 UDP:0 ICMP:0 Other:2>
如标红部分所示,多少个PacketList类相加,这里的算式就有多少个元素。故此,如果真的要将send函数的返回值相加,谨慎打印。
5.使用len函数计算send函数返回值中的数据包数量。
此外,使用PackekList的summary方法,可以按行打印其中储存的包的summary方法结果,即按行打印各个包的梗概。
当然,返回的PacketList有点鸡肋,毕竟那是我自己发出去的包。但是,在发送随机包的时候,的确可能需要返回发送的包。
据此,可以总结一些send函数的用法:
1.将count参数设为负值,由此死循环发送数据包,之后用Ctrl+C停止程序
from scapy.all import IP, TCP, send
packet = IP() / TCP()
send(packet, count=-1)
output:
┌──(matriller㉿Hack)-[~/桌面/Practice] └─$ sudo python demo.py ..................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................^C Sent 850 packets.
因为send函数中使用专门的变量来储存发包数,加上整个发包过程处于try-exept语句中,当按下Ctrl+C时,send函数捕获KeyboardInterrupt错误,之后根据传参决定是否展示信息,及传回None或PacketList对象。
这可以用于事先不知道要发送多少个包的情况。尽管使用while循环可以获得同样的效果,但是调用send函数的花销很大,每次循环都调用一次send函数,那么程序性能会明显下降。
2.传入inter参数,使每次发包之后停顿一段时间
from scapy.all import IP, TCP, send
from time import time, sleep
packet = IP() / TCP()
t = time()
send(packet, count=5, inter=0.5)
print(time()-t)
t = time()
for i in range(5):
send(packet)
sleep(0.5)
print(time()-t)
output:
┌──(matriller㉿Hack)-[~/桌面/Practice] └─$ sudo python demo.py ..... Sent 5 packets. 2.5401618480682373 . Sent 1 packets. . Sent 1 packets. . Sent 1 packets. . Sent 1 packets. . Sent 1 packets. 2.712531805038452
这个方法很重要。调用send函数的花销很大,将停顿的工作交由send函数底层去实现可以提高程序性能。在上方的输出中,仅仅只是发送5个包,使用for循环停顿的比起传参停顿的已经有明显的迟钝。而在网络编程中,要发送的绝不仅5个包。
3.向verbose传入假值关闭send函数自身的输出
from scapy.all import IP, TCP, send
from time import time, sleep
packet = IP() / TCP()
send(packet, verbose=0)
output:
┌──(matriller㉿Hack)-[~/桌面/Practice] └─$ sudo python demo.py
没有了每发一个包都要打印的点和结束时的总数。有时我们在使用Scapy时不希望send函数打印消息到屏幕,那么这个参数会大有用处。
由于send函数底层将verbose参数作为if语句的条件,向verbose传入下列值都可以得到相同的效果:(None不行)
0,0.0,'',b'',False,[],()
4.向x参数传入列表,提升发包速度
from scapy.all import IP, TCP, send
from time import time, sleep
packet = [IP() / TCP() for i in range(5)]
print(len(packet))
print(type(packet))
print(packet)
t = time()
send(packet)
print(time()-t)
t = time()
for i in range(5):
send(IP() / TCP())
print(time()-t)
output:
┌──(matriller㉿Hack)-[~/桌面/Practice]
└─$ sudo python demo.py
5
<class 'list'>
[<IP frag=0 proto=tcp |<TCP |>>, <IP frag=0 proto=tcp |<TCP |>>, <IP frag=0 proto=tcp |<TCP |>>, <IP frag=0 proto=tcp |<TCP |>>, <IP frag=0 proto=tcp |<TCP |>>]
.....
Sent 5 packets.
0.03799152374267578
.
Sent 1 packets.
.
Sent 1 packets.
.
Sent 1 packets.
.
Sent 1 packets.
.
Sent 1 packets.
0.1679677963256836
可以看到,向send函数传入列表以使其一次性发送,性能比使用for循环多次调用send函数性能要高的多。详情可以看我的另一篇文章:
源码部分:
def __gen_send(s, # type: SuperSocket
x, # type: _PacketIterable
inter=0, # type: int
loop=0, # type: int
count=None, # type: Optional[int]
verbose=None, # type: Optional[int]
realtime=False, # type: bool
return_packets=False, # type: bool
*args, # type: Any
**kargs # type: Any
):
# type: (...) -> Optional[PacketList]
"""
An internal function used by send/sendp to actually send the packets,
implement the send logic...
It will take care of iterating through the different packets
"""
if isinstance(x, str):
x = conf.raw_layer(load=x)
if not isinstance(x, Gen):
x = SetGen(x)
if verbose is None:
verbose = conf.verb
n = 0
if count is not None:
loop = -count
elif not loop:
loop = -1
sent_packets = PacketList() if return_packets else None
p = None
try:
while loop:
dt0 = None
for p in x:
if realtime:
ct = time.time()
if dt0:
st = dt0 + float(p.time) - ct
if st > 0:
time.sleep(st)
else:
dt0 = ct - float(p.time)
s.send(p)
if sent_packets is not None:
sent_packets.append(p)
n += 1
if verbose:
os.write(1, b".")
time.sleep(inter)
if loop < 0:
loop += 1
except KeyboardInterrupt:
pass
finally:
try:
cast(Packet, x).sent_time = cast(Packet, p).sent_time
except AttributeError:
pass
if verbose:
print("\nSent %i packets." % n)
return sent_packets
def _send(x, # type: _PacketIterable
_func, # type: Callable[[NetworkInterface], Type[SuperSocket]]
inter=0, # type: int
loop=0, # type: int
iface=None, # type: Optional[_GlobInterfaceType]
count=None, # type: Optional[int]
verbose=None, # type: Optional[int]
realtime=False, # type: bool
return_packets=False, # type: bool
socket=None, # type: Optional[SuperSocket]
**kargs # type: Any
):
# type: (...) -> Optional[PacketList]
"""Internal function used by send and sendp"""
need_closing = socket is None
iface = resolve_iface(iface or conf.iface)
socket = socket or _func(iface)(iface=iface, **kargs)
results = __gen_send(socket, x, inter=inter, loop=loop,
count=count, verbose=verbose,
realtime=realtime, return_packets=return_packets)
if need_closing:
socket.close()
return results
@conf.commands.register
def send(x, # type: _PacketIterable
iface=None, # type: Optional[_GlobInterfaceType]
**kargs # type: Any
):
# type: (...) -> Optional[PacketList]
"""
Send packets at layer 3
:param x: the packets
:param inter: time (in s) between two packets (default 0)
:param loop: send packet indefinitely (default 0)
:param count: number of packets to send (default None=1)
:param verbose: verbose mode (default None=conf.verb)
:param realtime: check that a packet was sent before sending the next one
:param return_packets: return the sent packets
:param socket: the socket to use (default is conf.L3socket(kargs))
:param iface: the interface to send the packets on
:param monitor: (not on linux) send in monitor mode
:returns: None
"""
iface = _interface_selection(iface, x)
return _send(
x,
lambda iface: iface.l3socket(),
iface=iface,
**kargs
)
参考文献:
浅析Python运算符重载_viclee108的博客-CSDN博客_python 运算符重载
scapy.sendrecv — Scapy 2.4.4. 文档
https://github.com/secdev/scapy/blob/master/scapy/sendrecv.py#L413-L44