diff --git a/README.md b/README.md index 420a86a..e0c425f 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ Supports the following info. Parsed info in bold: - Hop - **Hop counter** - - **[AS\#]** - Probe + - **[AS\#]** - **Hostname** - **(IP address)** - **RTT** @@ -26,20 +26,20 @@ Supports the following info. Parsed info in bold: # Usage -``` +```python import trparse -s = +s = "" # Parse the traceroute output traceroute = trparse.loads(s) # You can print the result -print traceroute +print(traceroute) # Save it as string tr_str = str(traceroute) # Or travel the tree hop = traceroute.hops[0] probe = hop.probes[0] # And print the IP address -print probe.ip +print(probe.ip) ``` # Data structures @@ -77,7 +77,7 @@ some tokens it expects to find in a specific format. For example: preceded by space characters). - **[AS\#]** must be surrounded by square brackets `[]` and start with `AS`. -- **Hostname** Can be a hostname or its IP address without parenthesis +- **Hostname** can be a hostname or its IP address without parenthesis. - **(IP address)** either IPv4 or IPv6 must surrounded by parenthesis `()`. - **RTT** must be in integer (without commas or dots) or float format @@ -91,6 +91,18 @@ work in a Widows system. Maybe in a future release. # Changelog +## v0.4.0 +1. Added Windows tracert compatibility +2. Fixed error when hostname not found +3. Added traceroute.global_rtt property +4. Remove empty lines from traceroutes +5. Added InvalidHeader exception when tracert/traceroute output is inconsistent +6. Improved test matrix + +## v0.3.1 +1. Add Python 3 compatibility +2. Update documentation + ## v0.3.0 1. Rebuilt the parsing function to support RTT, IP, Name and ASN in any order inside the hop/probe. 2. RTT values are now Decimal. diff --git a/setup.py b/setup.py index 2950670..43b75f1 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ ] setup(name='trparse', - version='0.3.0', + version='0.3.1', description='Traceroute parser', author='Luis Benitez', url='https://github.com/lbenitez000/trparse', diff --git a/tests/data/windows_fr.txt b/tests/data/windows_fr.txt new file mode 100644 index 0000000..8a87987 --- /dev/null +++ b/tests/data/windows_fr.txt @@ -0,0 +1,16 @@ +Détermination de l’itinéraire vers google.fr [172.217.22.131] +avec un maximum de 30 sauts : + + 1 2 ms 2 ms 5 ms my.local.network.local [192.168.85.24] + 2 9 ms 6 ms 6 ms 192.168.12.254 + 3 21 ms 20 ms 22 ms i15-les02-th2-7-48-3-2.dsl.dyn.abo.bbox.fr [7.48.3.2] + 4 * 18 ms 17 ms 62.34.2.72 + 5 20 ms 19 ms 19 ms 62.34.2.76 + 6 27 ms 21 ms 19 ms lag24.rpt02-mrs.net.bbox.fr [212.194.170.56] + 7 28 ms 29 ms 29 ms 209.85.148.0 + 8 30 ms 30 ms 30 ms 74.125.244.211 + 9 32 ms 50 ms 30 ms 216.239.35.209 + 10 31 ms 31 ms 30 ms 108.170.233.115 + 11 31 ms 32 ms 30 ms 108.170.245.1 + 12 30 ms 30 ms 30 ms 66.249.95.247 + 13 30 ms 29 ms 30 ms par21s12-in-f3.1e100.net [172.217.22.131] diff --git a/tests/data/windows_self.txt b/tests/data/windows_self.txt new file mode 100644 index 0000000..6a6d56c --- /dev/null +++ b/tests/data/windows_self.txt @@ -0,0 +1,6 @@ +Détermination de l’itinéraire vers myhost [127.0.0.1] +avec un maximum de 30 sauts : + + 1 <1 ms <1 ms <1 ms myhost [127.0.0.1] + +Itinéraire déterminé. diff --git a/trparse.py b/trparse.py index 5eb380b..59736ed 100644 --- a/trparse.py +++ b/trparse.py @@ -1,18 +1,34 @@ -# -*- coding: utf-8 -*- +#! /usr/bin/env python +# -*- coding: utf-8 -*- """ -Copyright (C) 2015 Luis Benitez - +trparse 2015-2022 written by: + - Orsiris de Jong (@deajan) 2020-2022 + - Rarylson Freitas (@rarylson) 2018-2020 + - Luis Benitez (@lbenitez000) Copyright (C) 2015-2019 + Parses the output of a traceroute execution into an AST (Abstract Syntax Tree). """ -import re +__intname__ = "trparse" +__author__ = "Luis Benitez, Rarylson Freitas, Orsiris de Jong" +__copyright__ = "Copyright (C) 2014-2019 Luis Benitez" +__license__ = "MIT License" +__version__ = "0.4.0" +__build__ = "2022053001" + +import re from decimal import Decimal -RE_HEADER = re.compile(r'(\S+)\s+\((?:(\d+\.\d+\.\d+\.\d+)|([0-9a-fA-F:]+))\)') +# Unix uses () for IPs whereas Windows uses [] +RE_HEADER = re.compile(r'(\S+)\s+(?:\(|\[)(?:(\d+\.\d+\.\d+\.\d+)|([0-9a-fA-F:]+))(?:\)|\])') + +# Again, we must search for () or [] here +RE_PROBE_NAME_IP = re.compile(r'(\S+)\s+(?:\(|\[)(?:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|([0-9a-fA-F:]+))(?:\)|\])+') +# Fallback when no hostname present (also happens on windows) +RE_PROBE_IP_ONLY = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})+') -RE_PROBE_NAME_IP = re.compile(r'(\S+)\s+\((?:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|([0-9a-fA-F:]+))\)+') RE_PROBE_ANNOTATION = re.compile(r'^(!\w*)$') RE_PROBE_TIMEOUT = re.compile(r'^(\*)$') @@ -31,12 +47,16 @@ def __init__(self, dest_name, dest_ip): self.dest_name = dest_name self.dest_ip = dest_ip self.hops = [] + self.global_rtt = None def add_hop(self, hop): self.hops.append(hop) + def update_global_rtt(self, rtt): + self.global_rtt = rtt + def __str__(self): - text = "Traceroute for %s (%s)\n\n" % (self.dest_name, self.dest_ip) + text = "Traceroute for {} ({})\n\n".format(self.dest_name, self.dest_ip) for hop in self.hops: text += str(hop) return text @@ -47,7 +67,7 @@ class Hop(object): Abstraction of a hop in a traceroute. """ def __init__(self, idx): - self.idx = idx # Hop count, starting at 1 + self.idx = idx # Hop count, starting at 1 (usually) self.probes = [] # Series of Probe instances def add_probe(self, probe): @@ -88,7 +108,7 @@ def __str__(self): if self.asn is not None: text += "[AS{:d}] ".format(self.asn) if self.rtt: - text += "{:s} ({:s}) {:1.3f} ms".format(self.name, self.ip, self.rtt) + text += "{} ({}) {:1.3f} ms".format(self.name, self.ip, self.rtt) else: text = "*" if self.annotation: @@ -100,10 +120,13 @@ def __str__(self): def loads(data): """Parser entry point. Parses the output of a traceroute execution""" - lines = data.splitlines() + # Remove empty lines + lines = [line for line in data.splitlines() if line != ""] # Get headers match_dest = RE_HEADER.search(lines[0]) + if not match_dest: + raise InvalidHeader dest_name = match_dest.group(1) dest_ip = match_dest.group(2) @@ -140,8 +163,14 @@ def loads(data): probe_name = probe_name_ip_match.group(1) probe_ip = probe_name_ip_match.group(2) or probe_name_ip_match.group(3) else: - probe_name = None - probe_ip = None + # Let's try to only get IP (happens on windows) + probe_ip_match = RE_PROBE_IP_ONLY.search(hop_string) + if probe_ip_match: + probe_name = probe_ip_match.group(1) + probe_ip = probe_ip_match.group(1) + else: + probe_name = None + probe_ip = None probe_rtt_annotations = RE_PROBE_RTT_ANNOTATION.findall(hop_string) @@ -164,6 +193,7 @@ def loads(data): annotation=probe_annotation ) hop.add_probe(probe) + traceroute.update_global_rtt(probe_rtt) return traceroute @@ -174,3 +204,6 @@ def load(data): class ParseError(Exception): pass + +class InvalidHeader(ParseError): + pass