scapy
scapy copied to clipboard
Speed up route import on Linux-based systems
Just wanted to submit this for tracking purposes – I can't promise a PR anytime soon.
Brief description
On startup, Scapy reads the entire kernel routing table (not just the main table, all tables) at a rate of ~700 routes per second. On my system, which has the full IPv6 BGP table (~70k routes) this means Scapy takes ~1m45s to start.
Perhaps part of the problem is that it reads /proc/net/route
and /proc/net/ipv6_route
– which are very slow on current Linux kernels. Route query using netlink is 6–8 times faster.
Environment
- Scapy version:
git-archive.devbad14cb1a
- Python version:
3.7
- Operating System:
Linux 4.9.x, 4.19.56, 5.1.x
How to reproduce
Add tens of thousands of IPv6 routes to the kernel, then start Scapy.
Actual result
Time it takes to start scapy:
# time echo "exit()" | scapy
[logo omitted]
>>>
real 1m44.910s
user 0m7.717s
sys 1m30.925s
Expected result
Scapy should start somewhat faster.
If possible, Scapy should use rtnetlink or ip -json route
(which in turn uses netlink). Maybe runtime detection of whether "ip -json" is supported. Maybe only load the "main" table and not all tables.
Related resources
Time it takes to get route dumps from the kernel, via Netlink and via /proc:
$ time ip -6 route list table all | wc -l
70831
real 0m0.529s
user 0m0.172s
sys 0m0.346s
$ time cat /proc/net/ipv6_route | wc -l
70787
real 0m23.524s
user 0m0.018s
sys 0m22.658s
Thanks for reporting the issue. Does sending packets work fine? I am afraid that the route lookup will be slow as it was not designed to handle a full-view.
It is reasonably fast (the first route lookup for a destination takes ~2 seconds until "Finished sending 1 packets" – not blazing fast, but good enough for an interactive tool).
...But I guess that's why the loading takes so long – not /proc/net/route but rather the route addition becoming slower and slower with each new route?
Though I still think it'd be beneficial to use a method that allows dumping only the main&local kernel routing tables and ignoring the non-default ones (in my case the full BGP view is in a non-default kernel table).
It is true that we could be looking for a more time-efficient method. This exact process was done for Windows some time ago, and we ended up performing C calls to get the routes faster.
It sounds much harder to do on Linux, though we could look into Netlink sockets: http://olegkutkov.me/2019/03/24/getting-linux-routing-table-using-netlink/
In fact, there are already multiple Python libraries:
- https://github.com/svinota/pyroute2 (quite a messy project. Lots of unfinished implementations (BSD, Windows...) that simply return
[]
) - https://github.com/facebook/gnlpy (by Facebook. Only 1-2 files)
Feel free to provide a PR if you have an idea ! I personally won't look into this before 2.4.3 is released, because we don't want to introduce new untested material so close to the release.
Update: A proper workaround woule be to simply start Scapy in a ip namespace, this way you can precisely control what it has access to on boot. If you don't want it to have your 2000 ipv6 routes, configure it this way.
It IS scapy's expected behavior to load and process all routes it has access to. Although it would be nice if it were faster. But this will be low-priority since the above workaround is very reasonable.