TCP Traceroute in python

Hussain ali picture Hussain ali · Nov 2, 2018 · Viewed 7.7k times · Source

I am writing a python script to perform 'TCP Traceroute'. I learned scapy is a useful library to do this but I`m not getting the results I need. Can anyone help me resolve this? I want the python script to generate similar results as command line.

I am using linux, python 2.7 with scapy 2.4. I am not sure why same ip addresses are shown for all the hops.

from scapy.layers.inet import traceroute

result, unans = traceroute('172.217.17.46', maxttl=30)
   for snd, rcv in result:
   print snd.ttl, rcv.src, snd.sent_time, rcv.time

When I run this code I get following results:

1 10.0.2.2 1541113255.58 1541113255.6
2 172.217.17.46 1541113255.58 1541113255.72
3 172.217.17.46 1541113255.58 1541113255.72
4 172.217.17.46 1541113255.58 1541113255.72
5 172.217.17.46 1541113255.59 1541113255.73
6 172.217.17.46 1541113255.59 1541113255.73
7 172.217.17.46 1541113255.6 1541113255.74
8 172.217.17.46 1541113255.6 1541113255.74
9 172.217.17.46 1541113255.6 1541113255.74
10 172.217.17.46 1541113255.61 1541113255.75
11 172.217.17.46 1541113255.61 1541113255.75
12 172.217.17.46 1541113255.61 1541113255.75
13 172.217.17.46 1541113255.62 1541113255.76
14 172.217.17.46 1541113255.62 1541113255.76
15 172.217.17.46 1541113255.62 1541113255.76
16 172.217.17.46 1541113255.62 1541113255.77
17 172.217.17.46 1541113255.63 1541113255.77
18 172.217.17.46 1541113255.63 1541113255.77
19 172.217.17.46 1541113255.63 1541113255.77
20 172.217.17.46 1541113255.63 1541113255.77
21 172.217.17.46 1541113255.64 1541113255.78
22 172.217.17.46 1541113255.64 1541113255.78
23 172.217.17.46 1541113255.64 1541113255.78
24 172.217.17.46 1541113255.64 1541113255.78
25 172.217.17.46 1541113255.65 1541113255.79
26 172.217.17.46 1541113255.65 1541113255.79
27 172.217.17.46 1541113255.65 1541113255.79
28 172.217.17.46 1541113255.66 1541113255.8
29 172.217.17.46 1541113255.66 1541113255.8
30 172.217.17.46 1541113255.66 1541113255.8

I want to get the same results which I get when I run tcptraceroute from command line: tcptraceroute 172.217.17.46

Result from command line:

Selected device en0, address 192.168.86.24, port 49618 for outgoing packets
Tracing the path to 172.217.17.46 on TCP port 80 (http), 30 hops max
 1  192.168.86.1  2.848 ms  1.224 ms  1.330 ms
 2  96.120.101.53  10.423 ms  13.646 ms  12.221 ms
 3  po-115-rur102.bellevue.wa.seattle.comcast.net (68.87.205.245)  18.877 ms  18.818 ms  12.593 ms
 4  be-103-ar01.seattle.wa.seattle.comcast.net (69.139.164.77)  15.188 ms  14.272 ms  14.005 ms
 5  be-33650-cr01.seattle.wa.ibone.comcast.net (68.86.93.165)  14.547 ms  15.273 ms  19.750 ms
 6  be-10846-pe01.seattle.wa.ibone.comcast.net (68.86.86.90)  14.546 ms  14.266 ms  13.521 ms
 7  50.242.150.242  14.159 ms  15.791 ms  14.037 ms
 8  74.125.243.195  14.635 ms  22.377 ms  13.558 ms
 9  72.14.236.174  15.051 ms  27.454 ms  14.312 ms
 10  108.170.235.60  66.430 ms  69.762 ms  68.606 ms
 11  216.239.58.255  85.531 ms  84.354 ms  85.303 ms
 12  172.253.51.157  153.310 ms  154.710 ms  153.375 ms
 13  209.85.142.166  157.376 ms  166.552 ms  157.562 ms
 14  216.239.43.37  170.523 ms  168.040 ms  158.182 ms
 15  108.170.241.225  158.953 ms  161.418 ms  169.103 ms
 16  108.170.236.137  158.561 ms  161.635 ms  157.510 ms
 17  ams16s29-in-f46.1e100.net (172.217.17.46) [open]  165.981 ms  160.451 ms  166.120 ms

Question1: Is scapy traceroute function really does TCP traceroute? Question2: I am new to scapy and traceroute, Is there something obvious I am missing in the code? Is there any other library which I can use if scapy is not suitable? I would really appreciate the help and any pointers.

NOTE: I WANT TO PERFORM TCP TRACE ROUTE FOR BOTH IPV6 AND IPV4.

Answer

Panos Kal. picture Panos Kal. · Nov 2, 2018

Is scapy traceroute function really does TCP traceroute?

Yes it does do TCP traceroute (among other things). Take a look at Scapy source-code:

    @conf.commands.register
    def traceroute(target, dport=80, minttl=1, maxttl=30, sport=RandShort(), l4 = None, filter=None, timeout=2, verbose=None, **kargs):
        """Instant TCP traceroute traceroute(target, [maxttl=30,] [dport=80,] [sport=80,] [verbose=conf.verb]) -> None"""
        if verbose is None:
            verbose = conf.verb
        if filter is None:
            # we only consider ICMP error packets and TCP packets with at
            # least the ACK flag set *and* either the SYN or the RST flag
            # set
            filter="(icmp and (icmp[0]=3 or icmp[0]=4 or icmp[0]=5 or icmp[0]=11 or icmp[0]=12)) or (tcp and (tcp[13] & 0x16 > 0x10))"
        if l4 is None:
            a,b = sr(IP(dst=target, id=RandShort(), ttl=(minttl,maxttl))/TCP(seq=RandInt(),sport=sport, dport=dport),
                     timeout=timeout, filter=filter, verbose=verbose, **kargs)
        else:
            # this should always work
            filter="ip"
            a,b = sr(IP(dst=target, id=RandShort(), ttl=(minttl,maxttl))/l4,
                     timeout=timeout, filter=filter, verbose=verbose, **kargs)
        a = TracerouteResult(a.res)
        if verbose:
            a.show()
        return a,b


I want to perform tcp traceroute for both ipv6 and ipv4

Again looking at Scapy source code, traceroot6 can do the job:

def traceroute6(target, dport=80, minttl=1, maxttl=30, sport=RandShort(), 
                l4 = None, timeout=2, verbose=None, **kargs):
    """
    Instant TCP traceroute using IPv6 :
    traceroute6(target, [maxttl=30], [dport=80], [sport=80]) -> None
    """
    if verbose is None:
        verbose = conf.verb

    if l4 is None:
        a,b = sr(IPv6(dst=target, hlim=(minttl,maxttl))/TCP(seq=RandInt(),sport=sport, dport=dport),
                 timeout=timeout, filter="icmp6 or tcp", verbose=verbose, **kargs)
    else:
        a,b = sr(IPv6(dst=target, hlim=(minttl,maxttl))/l4,
                 timeout=timeout, verbose=verbose, **kargs)
    a = TracerouteResult6(a.res)
    if verbose:
        a.display()
    return a,b

Example code for traceroute ipv6:

from scapy.all import *

waypoint = "2001:301:0:8002:203:47ff:fea5:3085"
target = "2001:5f9:4:7:2e0:81ff:fe52:9a6b"
traceroute6(waypoint, minttl=10, maxttl=40, l4=IPv6ExtHdrRouting(addresses=[target])/ICMPv6EchoRequest(data=RandString(7)))


I am not sure why same IP addresses are shown for all the hops.

Using a DNS traceroute by specifying a complete packet in the l4 parameter of traceroute() function you don't get same IP addresses for all the hops.

from scapy.all import *

target = ["172.217.17.46"]
result, unans = traceroute(target, l4=UDP(sport=RandShort())/DNS(qd=DNSQR(qname="www.google.com")))


Or you can use TCP SYN traceroute to obtain similar results:

from scapy.all import *

target = ["172.217.17.46"]
result, unans = sr(IP(dst=target, ttl=(1, 10)) / TCP(dport=53, flags="S"))
for snd, rcv in result:
  print(snd.ttl, rcv.src, snd.sent_time, rcv.time)


Or you can create IP and UDP packets to implement traceroute using Scapy like this:

from scapy.all import *
hostname = "172.217.17.46"
for i in range(1, 28):
    pkt = IP(dst=hostname, ttl=i) / UDP(dport=33434)
    reply = sr1(pkt, verbose=0)
    if reply is None:
        break
    elif reply.type == 3:
        print("Done!", reply.src)
        break
    else:
        print("%d hops away: " % i, reply.src, reply.time)


Is there any other library which I can use if Scapy is not suitable?

  • Check out webb. I haven't use it, but you can use it like this:
import webb
webb.traceroute("www.google.com")
webb.traceroute("www.google.com",'file-name.txt')
  • Also, check out this tcptraceroute by Thomas Guettler.

  • Or this Multi-source traceroute with geolocation implementation by Addy Yeow (Ayeowch).
    Among other things, using -j parameter (JSON_FILE), it will list sources in JSON file format.

  • Or this implementation by Christian Kreibich. It can parse traceroute info into a series of hop objects, each consisting of one or more probe results, likewise, object instances. Also, string formatting produces familiar traceroute output.
    (In order to work with Python 3 one needs to change cStringIO to from io import StringIO


Without scapy (using windows console):

Create a script named output.py containing the following:

import sys
from subprocess import Popen

if len(sys.argv) < 2:
    print('Usage: output.py "command to watch"')
    sys.exit(1)

cmd_line = sys.argv[1:]

p = Popen(cmd_line)
p.communicate()[0]

Example usage: python output.py ping google.com

Example output for ping:

Pinging google.com [216.58.209.14] with 32 bytes of data:
Reply from 216.58.209.14: bytes=32 time=50ms TTL=56
Reply from 216.58.209.14: bytes=32 time=45ms TTL=56
Reply from 216.58.209.14: bytes=32 time=45ms TTL=56
Reply from 216.58.209.14: bytes=32 time=45ms TTL=56

Ping statistics for 216.58.209.14:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 45ms, Maximum = 50ms, Average = 46ms

Example usage: python output.py tracert google.com

Example output for tracert:

Tracing route to google.com [172.217.18.174]
over a maximum of 30 hops:

  1    <1 ms     1 ms     1 ms  192.168.0.1
  2     6 ms     8 ms     8 ms  xx.xx.xx.xx
  3     8 ms     8 ms     8 ms  [xx.xx.xxx.xxx]
  4    17 ms    16 ms    16 ms  be3549.ccr31.sof02.atlas.cogentco.com [154.54.59.138]
  5    18 ms    17 ms    20 ms  be3421.ccr51.beg03.atlas.cogentco.com [130.117.0.94]
  6    32 ms    31 ms    30 ms  be3464.ccr52.vie01.atlas.cogentco.com [154.54.59.189]
  7    39 ms    37 ms    44 ms  be3462.ccr22.muc03.atlas.cogentco.com [154.54.59.182]
  8    42 ms    48 ms    44 ms  be2960.ccr42.fra03.atlas.cogentco.com [154.54.36.253]
  9    44 ms    50 ms    50 ms  be3187.agr41.fra03.atlas.cogentco.com [130.117.1.117]
 10    43 ms    45 ms    46 ms  tata.fra03.atlas.cogentco.com [130.117.15.86]
 11    45 ms    45 ms    44 ms  72.14.196.162
 12    43 ms    41 ms    46 ms  108.170.251.129
 13    46 ms    46 ms    45 ms  74.125.37.167
 14    45 ms    52 ms    48 ms  fra15s29-in-f14.1e100.net [172.217.18.174]

You can use tracert -d if you don't want names to be resolved.


For OSX and Linux and with Python 2.7 you can use this parser.