Slow performance with Scapy

I create a script that sends all traffic to Eth0 with Tap0 and sends Tap0 all traffic to Eth0. After I found many examples on the Internet, I managed to get it to work. The problem is that performance is very low.

Ping between two virtual machines without using a script takes less than 1 ms. Using a script, it takes ~ 15 ms.

When I send a 10 MB file from a virtual machine to another using scp, avg. transfer rate is 12 Mbps without a script. With a script, it drops to less than 1 Mbps.

I know that Python is actually not the fastest language for working with network traffic, but is it slow?

Is there any way to optimize this code?

My Ubuntu 10.04 32bit virtual machines.

Here is the code:

import os,sys,getopt,struct,re,string,logging from socket import * from fcntl import ioctl from select import select from scapy.all import * TUNSETIFF = 0x400454ca IFF_TAP = 0x0002 TUNMODE = IFF_TAP ETH_IFACE = "eth0" TAP_IFACE = "tap0" conf.iface = ETH_IFACE # Here we capture frames on ETH0 s = conf.L2listen(iface = ETH_IFACE) # Open /dev/net/tun in TAP (ether) mode (create TAP0) f = os.open("/dev/net/tun", os.O_RDWR) ifs = ioctl(f, TUNSETIFF, struct.pack("16sH", "tap%d", TUNMODE)) # Speed optimization so Scapy does not have to parse payloads Ether.payload_guess=[] os.system("ifconfig eth0 0.0.0.0") os.system("ifconfig tap0 192.168.40.107") os.system("ifconfig tap0 down") os.system("ifconfig tap0 hw ether 00:0c:29:7a:52:c4") os.system("ifconfig tap0 up") eth_hwaddr = get_if_hwaddr('eth0') while 1: r = select([f,s],[],[])[0] #Monitor f(TAP0) and s(ETH0) at the same time to see if a frame came in. #Frames from TAP0 if f in r: #If TAP0 received a frame # tuntap frame max. size is 1522 (ethernet, see RFC3580) + 4 tap_frame = os.read(f,1526) tap_rcvd_frame = Ether(tap_frame[4:]) sendp(tap_rcvd_frame,verbose=0) #Send frame to ETH0 #Frames from ETH0 if s in r: #If ETH0 received a frame eth_frame = s.recv(1522) if eth_frame.src != eth_hwaddr: # Add Tun/Tap header to frame, convert to string and send. "\x00\x00\x00\x00" is a requirement when writing to tap interfaces. It is an identifier for the Kernel. eth_sent_frame = "\x00\x00\x00\x00" + str(eth_frame) os.write(f, eth_sent_frame) #Send frame to TAP0 
+4
source share
2 answers

Honestly, I am surprised at his transformation, as well as that. I would be surprised if you could do much better than you already have.

Keep in mind the path that a packet must follow in order to cross its user bridge:

Entering one interface through the NIC driver in the kernel, it must wait for the context to switch to the user site, where it must clime the abstraction of the scapy protocol before it can be evaluated by your code. After which, your code sends back the abstractions of the scapy protocol (possibly reassembles the packet in the python user space), writes to the socket, waiting for the context to switch back to the kernel-ground, writes to the NIC driver, and finally the interface is sent ...

Now, when you look at this link, you measure the time taken to go through the whole process twice - once and once returned.

So that your context switches from the kernel to the user land 4 times (2 for each direction) and your way to do it in 0.015 seconds is pretty good.

+3
source

I had a similar problem: from a link that seemed to disable scapy source code

Each time you call send () or sendp (), Scapy will automatically create and close the socket for each packet you send! I see the convenience in this, makes the API much easier! But I'm ready for a bet that definitely hits performance!

Also a similar analysis here (link2) . This way you can optimize the following code from link2.

  #The code sample is from #https://home.regit.org/2014/04/speeding-up-scapy-packets-sending/ #also see https://byt3bl33d3r.imtqy.com/mad-max-scapy-improving-scapys-packet-sending-performance.html for similar sample. This works. def run(self): # open filename filedesc = open(self.filename, 'r') s = conf.L2socket(iface=self.iface) #added # loop on read line for line in filedesc: # Build and send packet # sendp(pkt, iface = self.iface, verbose = verbose) This line goes out s.send(pkt) #sendp() is replaced with send() 
+1
source

All Articles