Update top_traffic.py

This commit is contained in:
gru
2026-04-03 12:32:19 +02:00
parent 82eb3cbd7b
commit e236a13e8a

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env python3 #!/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 argparse
import collections import collections
@@ -53,15 +52,16 @@ class Packet(object):
class BucketSummary(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.start_ts = start_ts
self.end_ts = end_ts self.end_ts = end_ts
self.bytes_total = bytes_total self.bytes_total = bytes_total
self.packets_total = packets_total self.packets_total = packets_total
self.top_ips = top_ips self.top_ips = top_ips
self.top_pairs = top_pairs self.top_pairs = top_pairs
self.top_ports = top_ports
class CaptureError(RuntimeError): class CaptureError(RuntimeError):
@@ -396,13 +396,11 @@ def top_n(counter, limit):
return sorted(counter.items(), key=lambda kv: (-kv[1], kv[0]))[:limit] return sorted(counter.items(), key=lambda kv: (-kv[1], kv[0]))[:limit]
def validate_lengths(packets): def validate_lengths(packets):
if not packets: if not packets:
return return
max_len = max(p.length for p in packets) max_len = max(p.length for p in packets)
avg_len = sum(p.length for p in packets) / float(len(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: if max_len > 10 ** 7 or avg_len > 10 ** 6:
raise AnalysisError( raise AnalysisError(
"Parser zwrocil nierealne dlugosci pakietow (max={}, avg={:.1f}). " "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_ip_out = collections.Counter()
per_pair = collections.Counter() per_pair = collections.Counter()
per_proto = 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_bytes = collections.Counter()
bucket_packets = collections.Counter() bucket_packets = collections.Counter()
bucket_ip_bytes = collections.defaultdict(collections.Counter) bucket_ip_bytes = collections.defaultdict(collections.Counter)
bucket_pair_bytes = collections.defaultdict(collections.Counter) bucket_pair_bytes = collections.defaultdict(collections.Counter)
bucket_port_bytes = collections.defaultdict(collections.Counter)
local_total = collections.Counter() local_total = collections.Counter()
internal_pairs = 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: for p in packets:
src_is_local = p.src in local_ips src_is_local = p.src in local_ips
dst_is_local = p.dst in local_ips dst_is_local = p.dst in local_ips
ports_track = False
if local_ips: if local_ips:
if src_is_local and not dst_is_local: 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_ip = p.dst
bucket_key_pair = "{} -> {}".format(p.src, p.dst) bucket_key_pair = "{} -> {}".format(p.src, p.dst)
local_total[p.src] += p.length local_total[p.src] += p.length
ports_track = True
elif dst_is_local and not src_is_local: elif dst_is_local and not src_is_local:
per_ip_total[p.src] += p.length per_ip_total[p.src] += p.length
per_ip_in[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_ip = p.src
bucket_key_pair = "{} -> {}".format(p.src, p.dst) bucket_key_pair = "{} -> {}".format(p.src, p.dst)
local_total[p.dst] += p.length local_total[p.dst] += p.length
ports_track = True
elif src_is_local and dst_is_local: elif src_is_local and dst_is_local:
internal_pairs["{} -> {}".format(p.src, p.dst)] += p.length internal_pairs["{} -> {}".format(p.src, p.dst)] += p.length
local_total[p.src] += 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_src = p.src
bucket_key_ip_dst = p.dst bucket_key_ip_dst = p.dst
bucket_key_pair = "{} -> {}".format(p.src, p.dst) bucket_key_pair = "{} -> {}".format(p.src, p.dst)
ports_track = True
if p.proto: if p.proto:
per_proto[p.proto] += p.length 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_bytes[idx] += p.length
bucket_packets[idx] += 1 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 local_ips:
if bucket_key_ip is not None: if bucket_key_ip is not None:
bucket_ip_bytes[idx][bucket_key_ip] += p.length 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], packets_total=bucket_packets[idx],
top_ips=top_n(bucket_ip_bytes[idx], 3), top_ips=top_n(bucket_ip_bytes[idx], 3),
top_pairs=top_n(bucket_pair_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)) 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_ip_out": top_n(per_ip_out, top_ip_limit),
"per_pair": top_n(per_pair, top_pair_limit), "per_pair": top_n(per_pair, top_pair_limit),
"per_proto": top_n(per_proto, 10), "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, "buckets": buckets,
"interesting": interesting, "interesting": interesting,
"avg_bucket": avg_bucket, "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("Ruch poza lokalnymi IP: {}".format(human_bytes(stats.get("unmatched_bytes", 0))))
lines.append("") lines.append("")
def section(title, items, formatter=None): def section(title, items, formatter=None, show_avg=False):
lines.append(title) lines.append(title)
lines.append("-" * len(title)) lines.append("-" * len(title))
count = 0 count = 0
for key, value in items: for key, value in items:
count += 1 count += 1
label = formatter(key) if formatter else key 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: if count == 0:
lines.append("(brak danych)") lines.append("(brak danych)")
lines.append("") 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 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) 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) 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 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"]) section("Top protokoly", stats["per_proto"])
lines.append("Najciekawsze momenty") lines.append("Najciekawsze momenty")
@@ -622,6 +656,7 @@ def render_report(stats, resolve=False):
rate = bucket.bytes_total / duration rate = bucket.bytes_total / duration
top_ips = ", ".join("{} [{}]".format(pretty_ip(ip), human_bytes(b)) for ip, b in bucket.top_ips) 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_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( lines.append(
"{}. {} .. {} | {} | {} | pakiety={}".format( "{}. {} .. {} | {} | {} | pakiety={}".format(
idx, idx,
@@ -634,6 +669,7 @@ def render_report(stats, resolve=False):
) )
lines.append(" Top IP: {}".format(top_ips or "-")) lines.append(" Top IP: {}".format(top_ips or "-"))
lines.append(" Top pair: {}".format(top_pairs or "-")) lines.append(" Top pair: {}".format(top_pairs or "-"))
lines.append(" Top port: {}".format(top_ports or "-"))
lines.append("") lines.append("")
return "\n".join(lines) return "\n".join(lines)
@@ -652,7 +688,7 @@ def interactive_shell(stats, resolve):
inbound = dict(stats["per_ip_in"]) inbound = dict(stats["per_ip_in"])
outbound = dict(stats["per_ip_out"]) outbound = dict(stats["per_ip_out"])
print("Tryb interaktywny. Komendy: top, ip <addr>, moments, pairs, quit") print("Tryb interaktywny. Komendy: top, ip <addr>, moments, pairs, ports, quit")
while True: while True:
try: try:
raw = input("traffic> ").strip() raw = input("traffic> ").strip()
@@ -686,14 +722,28 @@ def interactive_shell(stats, resolve):
for i, (pair, b) in enumerate(stats["per_pair"], start=1): for i, (pair, b) in enumerate(stats["per_pair"], start=1):
print(" {:>2}. {:<52} {:>12}".format(i, pair[:52], human_bytes(b))) print(" {:>2}. {:<52} {:>12}".format(i, pair[:52], human_bytes(b)))
continue 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 "): if raw.startswith("ip "):
ip = raw[3:].strip() ip = raw[3:].strip()
print("{}".format(pretty(ip))) print("{}".format(pretty(ip)))
print(" total: {}".format(human_bytes(totals.get(ip, 0)))) print(" total: {}".format(human_bytes(totals.get(ip, 0))))
print(" inbound: {}".format(human_bytes(inbound.get(ip, 0)))) print(" inbound: {}".format(human_bytes(inbound.get(ip, 0))))
print(" outbound: {}".format(human_bytes(outbound.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 continue
print("Nieznana komenda. Uzyj: top, ip <addr>, moments, pairs, quit") print("Nieznana komenda. Uzyj: top, ip <addr>, moments, pairs, ports, quit")
def parse_args(): def parse_args():
@@ -793,6 +843,9 @@ def main():
"per_ip_out": stats["per_ip_out"], "per_ip_out": stats["per_ip_out"],
"per_pair": stats["per_pair"], "per_pair": stats["per_pair"],
"per_proto": stats["per_proto"], "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": [ "interesting": [
{ {
"start_ts": x.start_ts, "start_ts": x.start_ts,
@@ -801,6 +854,7 @@ def main():
"packets_total": x.packets_total, "packets_total": x.packets_total,
"top_ips": x.top_ips, "top_ips": x.top_ips,
"top_pairs": x.top_pairs, "top_pairs": x.top_pairs,
"top_ports": x.top_ports,
} }
for x in stats["interesting"] for x in stats["interesting"]
], ],