I’ve always wanted to write a reverse-traceroute system. However, writing my own Linux NetFilter module was daunting – I’m not a C programmer, let alone a kernel hacker. Then I came across Scapy.
While intercepting and mangling the packets in userspace does add some artificial latency, it is a good proof-of-concept. And of course, it supports both IPv4 and IPv6:
[python collapse=”true” title=”uturn.py” language=”true”]
#! /usr/bin/env python
from scapy.all import *
import nfqueue
import asyncore
from socket import AF_INET, AF_INET6
import struct
import threading
def uturn_4(query_orig):
# if the packet is expiring at this hop, send a TTL exceeded message
# this shouldn’t result in the traceroute ending
# (that’s usually indicated by a port-unreachable message for UDP/TCP traceroutes
# or an echo-reply for ICMP traceroutes)
if query_orig[IP].ttl is 1:
inflectionpoint = IP(src=query_orig[IP].dst, dst=query_orig[IP].src)/ICMP(type=11,code=0)/IPerror(str(query_orig))
send(inflectionpoint, verbose=0)
sys.stdout.flush()
return 1
# copy query_orig to query_tr_probe, so we can manipulate it
uturn_tr_probe = copy.deepcopy(query_orig)
# delete the IP checksum so it can be re-created
del uturn_tr_probe[IP].chksum
# decrement the TTL by one to make up for the inflection point
uturn_tr_probe[IP].ttl -= 1
# swap src and dst IP’s before sending
uturn_tr_probe[IP].src = query_orig[IP].dst
uturn_tr_probe[IP].dst = query_orig[IP].src
# send query_tr_probe, and get the ICMP ttl-exceeded response as uturn_tr_icmp
uturn_tr_icmp=sr1(uturn_tr_probe, timeout=1, nofilter=0, filter=”icmp[0] = 11 and icmp[1]=0″, verbose=0)
# if we got a response back from the sr1() function,
# and the response was an ICMP packet…
if uturn_tr_icmp is not None and IPerror in uturn_tr_icmp:
# copy the ICMP response to uturn_response
uturn_response = copy.deepcopy(uturn_tr_icmp)
# delete the IP checksum so it can be automatically re-created
del uturn_response[IP].chksum
# replace the IPerror message with the contents of the original packet
uturn_response[IPerror] = IPerror(str(query_orig))
# rewrite the dstIP to be that of the original query
uturn_response[IP].dst = query_orig[IP].src
# send our u-turned ICMP TTL exceeded message
send(uturn_response, verbose=0)
sys.stdout.flush()
return 1
def uturn_6(query_orig):
# if the packet is expiring at this hop, send a HLIM exceeded message
# this shouldn’t result in the traceroute ending
# (that’s usually indicated by a port-unreachable message for UDP/TCP traceroutes
# or an echo-reply for ICMP traceroutes)
if query_orig[IPv6].hlim is 1:
inflectionpoint = IPv6(src=query_orig[IPv6].dst, dst=query_orig[IPv6].src)/ICMPv6TimeExceeded(type=3,code=0)/IPerror6(str(query_orig))
send(inflectionpoint, verbose=0)
sys.stdout.flush()
return 1
# copy query_orig to query_tr_probe, but swap src/dst values
uturn_tr_probe = copy.deepcopy(query_orig)
# decrement the TTL by one to make up for the inflection point
uturn_tr_probe[IPv6].hlim -= 1
# swap src and dst IP’s before sending
uturn_tr_probe[IPv6].src = query_orig[IPv6].dst
uturn_tr_probe[IPv6].dst = query_orig[IPv6].src
# send query_tr_probe, and get the ICMPv6 ttl-exceeded response as uturn_tr_icmp
uturn_tr_icmp=sr1(uturn_tr_probe, timeout=1, nofilter=0, filter=”ip6[40] = 3″, verbose=0)
# if we got a response back from the sr1() function,
# and the response was an ICMPv6 Time Exceeded packet…
if uturn_tr_icmp is not None and ICMPv6TimeExceeded in uturn_tr_icmp:
# copy the ICMP response to uturn_response
uturn_response = copy.deepcopy(uturn_tr_icmp)
# delete the ICMP checksum so it can be automatically re-created
# note: scapy docs (i.e. ls(ICMPv6TimeExceeded) lists this is chksum
# but in reality, it is cksum — appears to be a typo/bug in scapy
del uturn_response[ICMPv6TimeExceeded].cksum
# replace the IPerror6 message with the contents of the original packet
uturn_response[IPerror6] = IPerror6(str(query_orig))
# rewrite the dstIP to be that of the original query
uturn_response[IPv6].dst = query_orig[IPv6].src
# send our u-turned ICMP TTL exceeded message
send(uturn_response, verbose=0)
sys.stdout.flush()
return 1
def cb(payload):
data = payload.get_data()
# check which IP version we’re working with
if IP(data).version == 4:
pkt = IP(data)
thread.start_new_thread(uturn_4, (pkt,))
elif IPv6(data).version == 6:
pkt = IPv6(data)
thread.start_new_thread(uturn_6, (pkt,))
payload.set_verdict(nfqueue.NF_DROP)
return 1
# stolen from somebody on stackoverflow
class AsyncNfQueue(asyncore.file_dispatcher):
“””An asyncore dispatcher of nfqueue events.
“””
def __init__(self, cb, nqueue=0, family=AF_INET, maxlen=5000, map=None):
self._q = nfqueue.queue()
self._q.set_callback(cb)
self._q.fast_open(nqueue, family)
self._q.set_queue_maxlen(maxlen)
self.fd = self._q.get_fd()
asyncore.file_dispatcher.__init__(self, self.fd, map)
self._q.set_mode(nfqueue.NFQNL_COPY_PACKET)
def handle_read(self):
self._q.process_pending(5)
# We don’t need to check for the socket to be ready for writing
def writable(self):
return False
async_queue = AsyncNfQueue(cb)
asyncore.loop(1) # time out of 1 second
[/python]
The Python/Scapy script gets its data from the Linux NFQUEUE. I use the following script to generate the iptables/ip6tables rules to divert incoming traceroutes to NFQUEUE:
[bash collapse=”true” title=”firewall-traceroute.sh”]
#!/bin/bash
# generate ipv4 chains
for i in {2..32}
do
printf “iptables -N TR%03d\n” $i
printf “iptables -A TR%03d -m recent –remove –name hop%03d\n” $i $((i-1))
printf “iptables -A TR%03d -m recent –set –name hop%03d -m ttl –ttl-eq %d -j NFQUEUE\n” $i $i $i
done
# first ipv4 hop input rule
printf “iptables -A INPUT -m recent –set –name hop001 -m ttl –ttl-eq 1 -j NFQUEUE\n”
# subsequent ipv4 hop intput rules
for i in {2..32}
do
printf “iptables -A INPUT -m recent –name hop%03d –rcheck –seconds 5 -j TR%03d\n” $((i-1)) $i
done
# generate ipv6 chains
for i in {2..32}
do
printf “ip6tables -N TR%03d\n” $i
printf “ip6tables -A TR%03d -m recent –remove –name hop%03d\n” $i $((i-1))
printf “ip6tables -A TR%03d -m recent –set –name hop%03d -m hl –hl-eq %d -j NFQUEUE\n” $i $i $i
done
# first ipv6 hop input rule
printf “ip6tables -A INPUT -m recent –set –name hop001 -m hl –hl-eq 1 -j NFQUEUE\n”
# subsequent ipv6 hop intput rules
for i in {2..32}
do
printf “ip6tables -A INPUT -m recent –name hop%03d –rcheck –seconds 5 -j TR%03d\n” $((i-1)) $i
done
[/bash]
Due to BCP 38 filtering, many hops after the inflection point are filtered (those networks are blocking packets spoofed from the interface addresses of their routers). Like all traceroute tests, you need to know how to interpret the results. However, the tool can still be interesting and useful. Here are some examples:
[code title=”example 1″ collapse=”true” highlight=”17″]
traceroute to adamkuj.net (216.139.253.42), 60 hops max, 60 byte packets
1 217.147.213.238 (217.147.213.238) 0.410 ms
2 veX.glb1-bb-r2.nexellent.net (77.245.28.34) 0.297 ms
3 ve1.zrh2-bb-r1.nexellent.net (77.245.29.1) 0.379 ms
4 ge-0-1-8-11.zur11.ip4.tinet.net (213.200.70.105) 0.705 ms
5 xe-2-2-2.fra21.ip4.tinet.net (89.149.183.73) 7.868 ms
6 as6461.ip4.tinet.net (77.67.73.34) 7.155 ms
7 xe-4-2-0.mpr1.cdg11.fr.above.net (64.125.22.197) 19.552 ms
8 xe-3-3-0.mpr1.lhr2.uk.above.net (64.125.24.85) 20.236 ms
9 xe-5-2-0.cr1.dca2.us.above.net (64.125.26.21) 99.282 ms
10 xe-4-0-0.cr1.iah1.us.above.net (64.125.31.245) 130.019 ms
11 xe-0-0-0.cr2.iah1.us.above.net (64.125.30.66) 131.137 ms
12 xe-0-1-0.mpr2.aus1.us.above.net (64.125.27.201) 133.523 ms
13 208.185.23.230.t01811-01.above.net (208.185.23.230) 133.315 ms
14 xe-3-3.dist-rtr-01.aus.us.siteprotect.com (216.139.253.66) 132.644 ms
15 xe-50.core-sw-01.aus.us.siteprotect.com (216.139.253.50) 133.324 ms
16 voodoo.adamkuj.net (216.139.253.42) 252.609 ms
17 vrid-26.core-sw.aus.us.siteprotect.com (216.139.253.33) 495.446 ms
18 xe-3-4.dist-rtr-01.aus.us.siteprotect.com (216.139.253.49) 462.565 ms
19 xe-1-3.brdr-rtr-01.aus.us.siteprotect.com (216.139.253.65) 462.311 ms
20 xe-1-2.brdr-rtr-02.aus.us.siteprotect.com (216.139.253.78) 445.989 ms
21 xe-5-2-0.edge3.Dallas1.Level3.net (4.59.112.37) 478.398 ms
22 *
23 *
24 *
25 *
26 *
27 *
28 *
29 ae-6-6.car1.Zurich1.Level3.net (4.69.133.230) 596.192 ms
30 NEXELLENT-A.car1.Zurich1.Level3.net (213.242.67.138) 593.508 ms
31 ve2.glb2-db-sw1.nexellent.net (77.245.28.35) 588.519 ms
32 *
33 *
34 *
…
[/code]
[code title=”example 2″ collapse=”true” highlight=”18″]
traceroute to adamkuj.net (2606:8200:0:1a:0:d88b:fd2a:2), 60 hops max, 80 byte packets
1 2a02:298:80:1337::1 (2a02:298:80:1337::1) 20.203 ms
2 vl19.cr1-sdf2.uk.uksolutions.net (2a02:298:0:1::25) 0.329 ms
3 te2-1.cr1-lon1.uk.uksolutions.net (2a02:298:0:1::16) 4.330 ms
4 xe-5-3-1-core0.tcsov.uk.as6908.net (2a01:450:2::31:1) 4.238 ms
5 lo0-core0.thest.uk.as6908.net (2a01:450::1:3) 4.477 ms
6 xe-0-0-0-5.r02.londen03.uk.bb.gin.ntt.net (2001:728:0:5000::59) 4.766 ms
7 po-5.r00.londen03.uk.bb.gin.ntt.net (2001:728:0:2000::5d) 119.543 ms
8 2001:438:ffff::407d:e49 (2001:438:ffff::407d:e49) 4.397 ms
9 *
10 *
11 *
12 *
13 2001:438:ffff::407d:1bc9 (2001:438:ffff::407d:1bc9) 120.184 ms
14 2001:438:fffe::51e (2001:438:fffe::51e) 123.732 ms
15 xe-3-3.dist-rtr-01.aus.us.siteprotect.com (2606:8200::fd42) 122.332 ms
16 2606:8200::fd32 (2606:8200::fd32) 128.577 ms
17 voodoo.adamkuj.net (2606:8200:0:1a:0:d88b:fd2a:2) 221.822 ms
18 2606:8200:0:1a::2 (2606:8200:0:1a::2) 421.865 ms
19 2606:8200::fd31 (2606:8200::fd31) 479.377 ms
20 xe-1-3.brdr-rtr-01.aus.us.siteprotect.com (2606:8200::fd41) 414.999 ms
21 2001:438:fffe::51d (2001:438:fffe::51d) 441.163 ms
22 *
23 *
24 *
25 2001:438:ffff::407d:cde (2001:438:ffff::407d:cde) 435.620 ms
26 ae-1.r20.dllstx09.us.bb.gin.ntt.net (2001:418:0:2000::d9) 446.681 ms
27 ae-3.r20.asbnva02.us.bb.gin.ntt.net (2001:418:0:2000::2cd) 476.665 ms
28 ae-0.r21.asbnva02.us.bb.gin.ntt.net (2001:418:0:2000::e) 466.413 ms
29 ae-2.r23.amstnl02.nl.bb.gin.ntt.net (2001:418:0:2000::1b2) 558.141 ms
30 ae-1.r03.amstnl02.nl.bb.gin.ntt.net (2001:728:0:2000::142) 557.867 ms
31 xe-0-0-0-23.r03.amstnl02.nl.ce.gin.ntt.net (2001:728:0:5000::26) 568.745 ms
32 *
33 *
34 *
35 lo0-core0.tcsov.uk.as6908.net (2a01:450::1:5) 569.270 ms
36 2a01:450:2::31:a (2a01:450:2::31:a) 560.775 ms
37 te9-1.cr1-sdf2.uk.uksolutions.net (2a02:298:0:1::15) 574.273 ms
38 vl19.cr1-ndf1.uk.uksolutions.net (2a02:298:0:1::26) 564.310 ms
39 *
40 *
41 *
…
[/code]
[code title=”example 3″ collapse=”true” highlight=”24″]
traceroute to adamkuj.net (216.139.253.42), 60 hops max, 60 byte packets
1 82.147.0.13 (82.147.0.13) 0.277 ms
2 te2-4.cr1-the.uk.6dg.co.uk (217.10.157.110) 5.589 ms
3 xe-4-3-1-core0.thest.uk.as6908.net (62.149.52.21) 5.554 ms
4 ae2-core0.thnor.uk.as6908.net (62.149.50.170) 5.717 ms
5 ae1-core1.thnor.uk.as6908.net (62.149.50.214) 7.703 ms
6 sl-crs2-lon-0-0-0-0.sprintlink.net (213.206.156.149) 6.307 ms
7 sl-crs1-lon-0-2-0-0.sprintlink.net (213.206.129.152) 6.653 ms
8 213.206.131.22 (213.206.131.22) 6.078 ms
9 ae-52-52.csw2.London1.Level3.net (4.69.139.120) 111.191 ms
10 ae-58-223.ebr2.London1.Level3.net (4.69.153.137) 110.796 ms
11 ae-41-41.ebr1.NewYork1.Level3.net (4.69.137.66) 109.873 ms
12 4.69.201.66 (4.69.201.66) 111.744 ms
13 ae-1-100.ebr1.Washington12.Level3.net (4.69.143.213) 109.478 ms
14 ae-6-6.ebr1.Atlanta2.Level3.net (4.69.148.105) 110.850 ms
15 ae-63-63.ebr3.Atlanta2.Level3.net (4.69.148.241) 109.592 ms
16 ae-7-7.ebr3.Dallas1.Level3.net (4.69.134.21) 112.102 ms
17 ae-63-63.csw1.Dallas1.Level3.net (4.69.151.133) 109.723 ms
18 ae-1-60.edge3.Dallas1.Level3.net (4.69.145.8) 111.042 ms
19 HOSTWAY-COR.edge3.Dallas1.Level3.net (4.59.112.38) 138.118 ms
20 xe-1-2.brdr-rtr-01.aus.us.siteprotect.com (216.139.253.77) 124.940 ms
21 xe-3-3.dist-rtr-01.aus.us.siteprotect.com (216.139.253.66) 123.130 ms
22 xe-50.core-sw-01.aus.us.siteprotect.com (216.139.253.50) 126.718 ms
23 voodoo.adamkuj.net (216.139.253.42) 168.105 ms
24 vrid-26.core-sw.aus.us.siteprotect.com (216.139.253.33) 442.778 ms
25 xe-3-4.dist-rtr-01.aus.us.siteprotect.com (216.139.253.49) 458.994 ms
26 xe-1-3.brdr-rtr-01.aus.us.siteprotect.com (216.139.253.65) 456.953 ms
27 xe-0-3-0.mpr2.aus1.us.above.net (208.185.23.229) 470.776 ms
28 *
29 *
30 *
31 *
32 *
33 *
34 *
35 *
36 *
37 te3-2-0-cr0.nik.nl.as6908.net (81.20.64.42) 577.000 ms
38 *
39 *
40 *
41 *
42 ae1-core0.sdsds.uk.as6908.net (62.149.50.105) 615.305 ms
43 62.149.52.10 (62.149.52.10) 569.237 ms
44 vl19-te2-2.cr1-sdn.uk.6dg.co.uk (217.10.157.142) 570.466 ms
45 *
46 *
47 *
48 *
…
[/code]
