1# Copyright (c) 2016 RIPE NCC 2# 3# This program is free software: you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation, either version 3 of the License, or 6# (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16from ..ipdetails import IP 17from .base import Renderer as BaseRenderer 18 19 20class Renderer(BaseRenderer): 21 22 RENDERS = [BaseRenderer.TYPE_TRACEROUTE] 23 24 DEFAULT_RADIUS = 2 25 26 @staticmethod 27 def add_arguments(parser): 28 group = parser.add_argument_group( 29 title="Optional arguments for traceroute_aspath renderer" 30 ) 31 group.add_argument( 32 "--traceroute-aspath-radius", 33 type=int, 34 help="Number of different ASs starting from the end of the " 35 "traceroute path. " 36 "Default: {}.".format(Renderer.DEFAULT_RADIUS), 37 metavar="RADIUS", 38 default=Renderer.DEFAULT_RADIUS 39 ) 40 41 def __init__(self, *args, **kwargs): 42 BaseRenderer.__init__(self, *args, **kwargs) 43 self.paths = {} 44 45 # Number of different ASs starting from the end of the traceroute path. 46 if "arguments" in kwargs: 47 self.RADIUS = kwargs["arguments"].traceroute_aspath_radius 48 else: 49 self.RADIUS = Renderer.DEFAULT_RADIUS 50 51 @staticmethod 52 def _get_asns_for_output(asns, radius): 53 asns_with_padding = [""] * radius + asns 54 asns_with_padding = asns_with_padding[-radius:] 55 return " ".join( 56 ["{:>8}".format("AS{}".format(asn) if asn else "") for asn in asns_with_padding] 57 ) 58 59 def on_start(self): 60 return "For each traceroute path toward the target, the " \ 61 "last {} ASNs will be shown\n\n".format(self.RADIUS) 62 63 def on_result(self, result): 64 65 ip_hops = [] 66 67 for hop in result.hops: 68 for packet in hop.packets: 69 if packet.origin: 70 ip_hops.append(packet.origin) 71 break 72 73 asns = [] 74 75 # starting from the last hop's IP, get up to <RADIUS> ASNs 76 for address in reversed(ip_hops): 77 ip = IP(address) 78 if ip.asn and ip.asn not in asns: 79 asns.append(ip.asn) 80 if len(asns) == self.RADIUS: 81 break 82 83 as_path = self._get_asns_for_output(list(reversed(asns)), self.RADIUS) 84 85 if as_path not in self.paths: 86 self.paths[as_path] = {} 87 self.paths[as_path]['cnt'] = 0 88 self.paths[as_path]['responded'] = 0 89 self.paths[as_path]['cnt'] += 1 90 if result.destination_ip_responded: 91 self.paths[as_path]['responded'] += 1 92 93 return "Probe #{:<5}: {}, {}completed\n".format( 94 result.probe_id, as_path, 95 "NOT " if not result.destination_ip_responded else "" 96 ) 97 98 def additional(self, results): 99 s = "\nNumber of probes for each AS path:\n\n" 100 101 for as_path in self.paths: 102 s += " {}: {} probe{}, {} completed\n".format( 103 as_path, 104 self.paths[as_path]['cnt'], 105 "s" if self.paths[as_path]['cnt'] > 1 else "", 106 self.paths[as_path]['responded'] 107 ) 108 109 return s 110