| #!/usr/bin/env python3 | |
| # Copyright 2021 The ChromiumOS Authors | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| from subprocess import PIPE | |
| import subprocess | |
| import sys | |
| # Must be in sync with src/platform2/patchpanel/routing_service.h | |
| MARK_TO_SOURCES = { | |
| "0x0/0x3f00": "UNKNOWN", | |
| "0x100/0x3f00": "CHROME", | |
| "0x200/0x3f00": "USER", | |
| "0x300/0x3f00": "UPDATE_ENGINE", | |
| "0x400/0x3f00": "SYSTEM", | |
| "0x500/0x3f00": "HOST_VPN", | |
| "0x2000/0x3f00": "ARC", | |
| "0x2100/0x3f00": "CROSVM", | |
| "0x2200/0x3f00": "PLUGINVM", | |
| "0x2300/0x3f00": "TETHERING", | |
| "0x2400/0x3f00": "ARC_VPN", | |
| } | |
| # Helper for executing a subprocess and obtaining the standard output. | |
| def command(args): | |
| return map(str, subprocess.run(args, stdout=PIPE).stdout.splitlines()) | |
| # Resets traffic counters in iptables. This will confuse shill traffic accounting | |
| # logic and should only be used for testing. | |
| def reset_counters(ipv4, ipv6): | |
| if ipv4: | |
| command(["iptables", "-t", "mangle", "-Z", "-w"]) | |
| if ipv6: | |
| command(["ip6tables", "-t", "mangle", "-Z", "-w"]) | |
| def easy_unit(counter): | |
| counter = int(counter) | |
| suffix = "B" | |
| if counter >= 1024: | |
| suffix = "K" | |
| counter = counter / 1024 | |
| if counter >= 1024: | |
| suffix = "M" | |
| counter = counter / 1024 | |
| if counter >= 1024: | |
| suffix = "G" | |
| counter = counter / 1024 | |
| return str(int(counter)) + suffix | |
| # Print traffic counters grouped by network interface and source. | |
| def print_counters(ipv4, ipv6, easy_units): | |
| # Extract interfaces for which counter chains are defined. | |
| # We are looking for jump chains like "-A POSTROUTING -o wlan0 -c 13947 1698015 -j tx_wlan0" | |
| postrouting_chains = command(["iptables", "-t", "mangle", "-v", "-S", "POSTROUTING", "-w"]) | |
| ifaces = [x.split()[3] for x in postrouting_chains if "tx_" in x] | |
| # Create a nested map of iface -> source -> {tx,rx}_{ipv4,ipv6} buckets to store counters. | |
| counters = { iface: {s: {} for s in MARK_TO_SOURCES.values()} for iface in ifaces} | |
| # For every iface, dump all the tx and rx counters for both ipv4 and ipv6. | |
| for iface in ifaces: | |
| counter_commands = {} | |
| if ipv4: | |
| counter_commands["tx_ipv4"] = ["iptables", "-t", "mangle", "-v", "-S", "tx_" + iface, "-w"] | |
| counter_commands["rx_ipv4"] = ["iptables", "-t", "mangle", "-v", "-S", "rx_" + iface, "-w"] | |
| if ipv6: | |
| counter_commands["tx_ipv6"] = ["ip6tables", "-t", "mangle", "-v", "-S", "tx_" + iface, "-w"] | |
| counter_commands["rx_ipv6"] = ["ip6tables", "-t", "mangle", "-v", "-S", "rx_" + iface, "-w"] | |
| # Extract counters from subchains for all source rules looking like: | |
| # "-A tx_eth0 -m mark --mark 0x100/0x3f00 -c 33456 8637724 -j RETURN" | |
| for bucket, arg in counter_commands.items(): | |
| for counter in command(arg): | |
| atoms = counter.split() | |
| if len(atoms) == 5: | |
| counters[iface]["UNKNOWN"][bucket] = atoms[4][:-1] | |
| continue | |
| if len(atoms) < 9: | |
| continue | |
| source = MARK_TO_SOURCES.get(atoms[5]) | |
| if source: | |
| counters[iface][source][bucket] = atoms[8] | |
| # Print the output | |
| for iface, tail in counters.items(): | |
| for source, buckets in tail.items(): | |
| if sum(map(int, buckets.values())) == 0: | |
| continue | |
| o = "" | |
| for bucket, counter in buckets.items(): | |
| if counter == "0": | |
| continue | |
| if easy_units: | |
| counter = easy_unit(counter) | |
| o += bucket + "=" + counter + " " | |
| print(iface, source, o) | |
| HELP = [ | |
| "show|print|list: print traffic counters grouped by network interface", | |
| "reset: reset traffic counters", | |
| "help: print this help information", | |
| "options:", | |
| " -h: use unit suffixes B (byte), K (kilobyte), M (megabyte), G (gigabyte). By default counters are displayed as total bytes", | |
| " -4: print or reset IPv4 counters", | |
| " -6: print or reset IPv6 counters", | |
| " (by default both -4 and -6 are passed implicitly)", | |
| ] | |
| def print_help(): | |
| for l in HELP: | |
| print(l, file=sys.stderr) | |
| def main(): | |
| easy_units = "-h" in sys.argv | |
| ipv4 = "-6" not in sys.argv | |
| ipv6 = "-4" not in sys.argv | |
| if not ipv4 and not ipv6: | |
| ipv4 = True | |
| ipv6 = True | |
| if len(sys.argv) < 2 or sys.argv[1] == "help": | |
| print_help() | |
| return 0 | |
| if sys.argv[1] in ["show", "print", "list"]: | |
| print_counters(ipv4, ipv6, easy_units) | |
| return 0 | |
| if sys.argv[1] == "reset": | |
| reset_counters(ipv4, ipv6) | |
| return 0 | |
| print("incorrect arguments", file=sys.stderr) | |
| print_help() | |
| return 1 | |
| if __name__ == '__main__': | |
| sys.exit(main()) |