I've been using Scapy for years and one thing that's always bothered me was it's performace, especially when it comes to sending packets, to give you an idea:
from scapy.all import *
for i in range(0, 10):
send(ARP(pdst='192.168.1.88',
psrc='192.168.1.11',
hwdst='f0:84:2f:fb:a8:89',
op='is-at'),
iface='enp3s0', verbose=False)
print 'sent packet'
Simple enough, were just sending 10 ARP packets:
time sudo python normal_send.py
WARNING: No route found for IPv6 destination :: (no default route?)
sent packet
sent packet
sent packet
sent packet
sent packet
sent packet
sent packet
sent packet
sent packet
sent packet
sudo -E python normal_send.py 0.38s user 0.04s system 32% cpu 1.311 total
So it took us 1.311 seconds to send 10 packets?? Boooo! That's way too long! Surely there must be something we can do to speed things up a bit!
After downloading the source, I took a look at the send() function in the sendrecv.py file:
@conf.commands.register
def send(x, inter=0, loop=0, count=None, verbose=None, realtime=None, *args, **kargs):
"""Send packets at layer 3 send(packets, [inter=0], [loop=0], [verbose=conf.verb]) -> None"""
__gen_send(conf.L3socket(*args, **kargs), x, inter=inter, loop=loop, count=count,verbose=verbose, realtime=realtime)
Well ok, looks like it's creating a socket and passing it the function arguments, lets take a look at __gen_send():
def __gen_send(s, x, inter=0, loop=0, count=None, verbose=None, realtime=None, *args, **kargs):
if type(x) is 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
try:
while loop:
dt0 = None
for p in x:
if realtime:
ct = time.time()
if dt0:
st = dt0+p.time-ct
if st > 0:
time.sleep(st)
else:
dt0 = ct-p.time
s.send(p)
n += 1
if verbose:
os.write(1,".")
time.sleep(inter)
if loop < 0:
loop += 1
except KeyboardInterrupt:
pass
s.close()
if verbose:
print "\nSent %i packets." % n
At first glance, nothing out of the ordinary: just doing some basic type checking, setting some instance variables and looping if the loop argument is set. At second glance I noticed that pesky s.close()! It's closing the socket!
Every time you invoke send() or sendp() Scapy will automatically create and close a socket for every packet you send! I can see convenience in that, makes the API much simpler! But I'm willing to bet that definitely takes a hit on performance!
Grep'ing the source, we can see other places where it's used:
scapy/config.py:342: L3socket = None
scapy/supersocket.py:138:if conf.L3socket is None:
scapy/supersocket.py:139: conf.L3socket = L3RawSocket
scapy/sendrecv.py:251: __gen_send(conf.L3socket(*args, **kargs), x, inter=inter, loop=loop, count=count,verbose=verbose, realtime=realtime)
scapy/sendrecv.py:316: s = conf.L3socket(filter=filter, iface=iface, nofilter=nofilter)
scapy/sendrecv.py:334: s=conf.L3socket(filter=filter, nofilter=nofilter, iface=iface)
scapy/sendrecv.py:507: s = conf.L3socket(filter=filter, iface=iface, nofilter=nofilter)
scapy/arch/pcapdnet.py:364: conf.L3socket=L3dnetSocket
scapy/arch/linux.py:517:conf.L3socket = L3PacketSocket
scapy/layers/inet6.py:2946: if not conf.L3socket == _IPv6inIP:
scapy/layers/inet6.py:2947: _IPv6inIP.cls = conf.L3socket
scapy/layers/inet6.py:2949: del(conf.L3socket)
scapy/layers/inet.py:1493: s=conf.L3socket()
scapy/scapypipes.py:83: self.s = conf.L3socket(iface=self.iface)
scapy/automaton.py:440: self.send_sock_class = kargs.pop("ll", conf.L3socket)
doc/scapy/troubleshooting.rst:19: >>> conf.L3socket
doc/scapy/troubleshooting.rst:21: >>> conf.L3socket=L3RawSocket
doc/scapy/usage.rst:508: >>> conf.L3socket=L3dnetSocket
Cool! Judging from scapypipes.py line 83 , all we need to do to open a socket is:
s = conf.L3socket(iface=interface)
Let's try it out!
from scapy.all import *
s = conf.L3socket(iface='enp3s0')
for i in range(0, 10):
s.send(ARP(pdst='192.168.1.88',
psrc='192.168.1.11',
hwdst='f0:84:2f:fb:a8:89',
op='is-at')
)
print 'sent packet'
And when run:
time sudo python madmax_send.py
WARNING: No route found for IPv6 destination :: (no default route?)
sent packet
sent packet
sent packet
sent packet
sent packet
sent packet
sent packet
sent packet
sent packet
sent packet
sudo -E python madmax_send.py 0.36s user 0.02s system 82% cpu 0.464 total
Holy packets Batman! That's almost 2x the speed! \o/
Go Top