diff --git a/top_traffic.py b/top_traffic.py index 43e4c77..87e909b 100644 --- a/top_traffic.py +++ b/top_traffic.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 -# example: python3 top_traffic.py -d 300 --resolve --server-ip 10.20.10.11 -i bond0 --delete-pcap - +# example: python3 top_traffic.py -d 300 --resolve --server-ip 10.20.10.11 -i bond0 --delete-pcap import argparse import collections @@ -53,15 +52,16 @@ class Packet(object): class BucketSummary(object): - __slots__ = ("start_ts", "end_ts", "bytes_total", "packets_total", "top_ips", "top_pairs") + __slots__ = ("start_ts", "end_ts", "bytes_total", "packets_total", "top_ips", "top_pairs", "top_ports") - def __init__(self, start_ts, end_ts, bytes_total, packets_total, top_ips, top_pairs): + def __init__(self, start_ts, end_ts, bytes_total, packets_total, top_ips, top_pairs, top_ports): self.start_ts = start_ts self.end_ts = end_ts self.bytes_total = bytes_total self.packets_total = packets_total self.top_ips = top_ips self.top_pairs = top_pairs + self.top_ports = top_ports class CaptureError(RuntimeError): @@ -396,13 +396,11 @@ def top_n(counter, limit): return sorted(counter.items(), key=lambda kv: (-kv[1], kv[0]))[:limit] - def validate_lengths(packets): if not packets: return max_len = max(p.length for p in packets) avg_len = sum(p.length for p in packets) / float(len(packets)) - # Ethernet/IP packets from tcpdump -r should not produce absurd frame sizes. if max_len > 10 ** 7 or avg_len > 10 ** 6: raise AnalysisError( "Parser zwrocil nierealne dlugosci pakietow (max={}, avg={:.1f}). " @@ -426,10 +424,15 @@ def analyze_packets(packets, bucket_seconds, top_ip_limit, top_pair_limit, local per_ip_out = collections.Counter() per_pair = collections.Counter() per_proto = collections.Counter() + per_port_total = collections.Counter() + per_port_src = collections.Counter() + per_port_dst = collections.Counter() + bucket_bytes = collections.Counter() bucket_packets = collections.Counter() bucket_ip_bytes = collections.defaultdict(collections.Counter) bucket_pair_bytes = collections.defaultdict(collections.Counter) + bucket_port_bytes = collections.defaultdict(collections.Counter) local_total = collections.Counter() internal_pairs = collections.Counter() @@ -438,6 +441,7 @@ def analyze_packets(packets, bucket_seconds, top_ip_limit, top_pair_limit, local for p in packets: src_is_local = p.src in local_ips dst_is_local = p.dst in local_ips + ports_track = False if local_ips: if src_is_local and not dst_is_local: @@ -447,6 +451,7 @@ def analyze_packets(packets, bucket_seconds, top_ip_limit, top_pair_limit, local bucket_key_ip = p.dst bucket_key_pair = "{} -> {}".format(p.src, p.dst) local_total[p.src] += p.length + ports_track = True elif dst_is_local and not src_is_local: per_ip_total[p.src] += p.length per_ip_in[p.src] += p.length @@ -454,6 +459,7 @@ def analyze_packets(packets, bucket_seconds, top_ip_limit, top_pair_limit, local bucket_key_ip = p.src bucket_key_pair = "{} -> {}".format(p.src, p.dst) local_total[p.dst] += p.length + ports_track = True elif src_is_local and dst_is_local: internal_pairs["{} -> {}".format(p.src, p.dst)] += p.length local_total[p.src] += p.length @@ -473,6 +479,7 @@ def analyze_packets(packets, bucket_seconds, top_ip_limit, top_pair_limit, local bucket_key_ip_src = p.src bucket_key_ip_dst = p.dst bucket_key_pair = "{} -> {}".format(p.src, p.dst) + ports_track = True if p.proto: per_proto[p.proto] += p.length @@ -481,6 +488,18 @@ def analyze_packets(packets, bucket_seconds, top_ip_limit, top_pair_limit, local bucket_bytes[idx] += p.length bucket_packets[idx] += 1 + if ports_track: + if p.sport: + sport = str(p.sport) + per_port_src[sport] += p.length + per_port_total[sport] += p.length + bucket_port_bytes[idx]["src:{}".format(sport)] += p.length + if p.dport: + dport = str(p.dport) + per_port_dst[dport] += p.length + per_port_total[dport] += p.length + bucket_port_bytes[idx]["dst:{}".format(dport)] += p.length + if local_ips: if bucket_key_ip is not None: bucket_ip_bytes[idx][bucket_key_ip] += p.length @@ -503,6 +522,7 @@ def analyze_packets(packets, bucket_seconds, top_ip_limit, top_pair_limit, local packets_total=bucket_packets[idx], top_ips=top_n(bucket_ip_bytes[idx], 3), top_pairs=top_n(bucket_pair_bytes[idx], 3), + top_ports=top_n(bucket_port_bytes[idx], 3), ) ) buckets.sort(key=lambda b: (-b.bytes_total, b.start_ts)) @@ -544,6 +564,9 @@ def analyze_packets(packets, bucket_seconds, top_ip_limit, top_pair_limit, local "per_ip_out": top_n(per_ip_out, top_ip_limit), "per_pair": top_n(per_pair, top_pair_limit), "per_proto": top_n(per_proto, 10), + "per_port_total": top_n(per_port_total, top_ip_limit), + "per_port_src": top_n(per_port_src, top_ip_limit), + "per_port_dst": top_n(per_port_dst, top_ip_limit), "buckets": buckets, "interesting": interesting, "avg_bucket": avg_bucket, @@ -597,22 +620,33 @@ def render_report(stats, resolve=False): lines.append("Ruch poza lokalnymi IP: {}".format(human_bytes(stats.get("unmatched_bytes", 0)))) lines.append("") - def section(title, items, formatter=None): + def section(title, items, formatter=None, show_avg=False): lines.append(title) lines.append("-" * len(title)) count = 0 for key, value in items: count += 1 label = formatter(key) if formatter else key - lines.append("{:>2}. {:<52} {:>12}".format(count, label[:52], human_bytes(value))) + if show_avg: + avg = value / max(0.001, stats["duration"]) + lines.append( + "{:>2}. {:<52} {:>12} avg={:>12}".format( + count, label[:52], human_bytes(value), human_bps(avg) + ) + ) + else: + lines.append("{:>2}. {:<52} {:>12}".format(count, label[:52], human_bytes(value))) if count == 0: lines.append("(brak danych)") lines.append("") - section("Top zdalne IP lacznie" if stats.get("local_mode") else "Top IP lacznie", stats["per_ip_total"], pretty_ip) - section("Top zdalne IP inbound do serwera" if stats.get("local_mode") else "Top IP inbound do serwera", stats["per_ip_in"], pretty_ip) - section("Top zdalne IP outbound z serwera" if stats.get("local_mode") else "Top IP outbound z serwera", stats["per_ip_out"], pretty_ip) + section("Top zdalne IP lacznie" if stats.get("local_mode") else "Top IP lacznie", stats["per_ip_total"], pretty_ip, show_avg=True) + section("Top zdalne IP inbound do serwera" if stats.get("local_mode") else "Top IP inbound do serwera", stats["per_ip_in"], pretty_ip, show_avg=True) + section("Top zdalne IP outbound z serwera" if stats.get("local_mode") else "Top IP outbound z serwera", stats["per_ip_out"], pretty_ip, show_avg=True) section("Top pary serwer <-> zdalny host" if stats.get("local_mode") else "Top pary src -> dst", stats["per_pair"]) + section("Top porty lacznie", stats["per_port_total"]) + section("Top porty source", stats["per_port_src"]) + section("Top porty destination", stats["per_port_dst"]) section("Top protokoly", stats["per_proto"]) lines.append("Najciekawsze momenty") @@ -622,6 +656,7 @@ def render_report(stats, resolve=False): rate = bucket.bytes_total / duration top_ips = ", ".join("{} [{}]".format(pretty_ip(ip), human_bytes(b)) for ip, b in bucket.top_ips) top_pairs = ", ".join("{} [{}]".format(pair, human_bytes(b)) for pair, b in bucket.top_pairs) + top_ports = ", ".join("{} [{}]".format(port, human_bytes(b)) for port, b in bucket.top_ports) lines.append( "{}. {} .. {} | {} | {} | pakiety={}".format( idx, @@ -634,6 +669,7 @@ def render_report(stats, resolve=False): ) lines.append(" Top IP: {}".format(top_ips or "-")) lines.append(" Top pair: {}".format(top_pairs or "-")) + lines.append(" Top port: {}".format(top_ports or "-")) lines.append("") return "\n".join(lines) @@ -652,7 +688,7 @@ def interactive_shell(stats, resolve): inbound = dict(stats["per_ip_in"]) outbound = dict(stats["per_ip_out"]) - print("Tryb interaktywny. Komendy: top, ip , moments, pairs, quit") + print("Tryb interaktywny. Komendy: top, ip , moments, pairs, ports, quit") while True: try: raw = input("traffic> ").strip() @@ -686,14 +722,28 @@ def interactive_shell(stats, resolve): for i, (pair, b) in enumerate(stats["per_pair"], start=1): print(" {:>2}. {:<52} {:>12}".format(i, pair[:52], human_bytes(b))) continue + if raw == "ports": + print("Top porty lacznie:") + for i, (port, b) in enumerate(stats["per_port_total"], start=1): + print(" {:>2}. {:<20} {:>12}".format(i, port[:20], human_bytes(b))) + print("Top porty source:") + for i, (port, b) in enumerate(stats["per_port_src"], start=1): + print(" {:>2}. {:<20} {:>12}".format(i, port[:20], human_bytes(b))) + print("Top porty destination:") + for i, (port, b) in enumerate(stats["per_port_dst"], start=1): + print(" {:>2}. {:<20} {:>12}".format(i, port[:20], human_bytes(b))) + continue if raw.startswith("ip "): ip = raw[3:].strip() print("{}".format(pretty(ip))) print(" total: {}".format(human_bytes(totals.get(ip, 0)))) print(" inbound: {}".format(human_bytes(inbound.get(ip, 0)))) print(" outbound: {}".format(human_bytes(outbound.get(ip, 0)))) + print(" avg total: {}".format(human_bps(totals.get(ip, 0) / max(0.001, stats["duration"])))) + print(" avg inbound: {}".format(human_bps(inbound.get(ip, 0) / max(0.001, stats["duration"])))) + print(" avg outbound: {}".format(human_bps(outbound.get(ip, 0) / max(0.001, stats["duration"])))) continue - print("Nieznana komenda. Uzyj: top, ip , moments, pairs, quit") + print("Nieznana komenda. Uzyj: top, ip , moments, pairs, ports, quit") def parse_args(): @@ -793,6 +843,9 @@ def main(): "per_ip_out": stats["per_ip_out"], "per_pair": stats["per_pair"], "per_proto": stats["per_proto"], + "per_port_total": stats["per_port_total"], + "per_port_src": stats["per_port_src"], + "per_port_dst": stats["per_port_dst"], "interesting": [ { "start_ts": x.start_ts, @@ -801,6 +854,7 @@ def main(): "packets_total": x.packets_total, "top_ips": x.top_ips, "top_pairs": x.top_pairs, + "top_ports": x.top_ports, } for x in stats["interesting"] ], @@ -828,4 +882,4 @@ def main(): if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file