|
6 | 6 | import argparse
|
7 | 7 | import sys
|
8 | 8 | import ipaddress
|
| 9 | +import json |
9 | 10 | import math
|
| 11 | +from collections import defaultdict |
10 | 12 |
|
11 | 13 | import asmap
|
12 | 14 |
|
@@ -113,6 +115,18 @@ def main():
|
113 | 115 | parser_diff.add_argument('infile2', type=argparse.FileType('rb'),
|
114 | 116 | help="second file to compare (text or binary)")
|
115 | 117 |
|
| 118 | + parser_diff_addrs = subparsers.add_parser("diff_addrs", |
| 119 | + help="compute difference between two asmap files for a set of addresses") |
| 120 | + parser_diff_addrs.add_argument('-s', '--show-addresses', dest="show_addresses", default=False, action="store_true", |
| 121 | + help="include reassigned addresses in the output") |
| 122 | + parser_diff_addrs.add_argument("infile1", type=argparse.FileType("rb"), |
| 123 | + help="first file to compare (text or binary)") |
| 124 | + parser_diff_addrs.add_argument("infile2", type=argparse.FileType("rb"), |
| 125 | + help="second file to compare (text or binary)") |
| 126 | + parser_diff_addrs.add_argument("addrs_file", type=argparse.FileType("r"), |
| 127 | + help="address file containing getnodeaddresses output to use in the comparison " |
| 128 | + "(make sure to set the count parameter to zero to get all node addresses, " |
| 129 | + "e.g. 'bitcoin-cli getnodeaddresses 0 > addrs.json')") |
116 | 130 | args = parser.parse_args()
|
117 | 131 | if args.subcommand is None:
|
118 | 132 | parser.print_help()
|
@@ -148,6 +162,33 @@ def main():
|
148 | 162 | f"# {ipv4_changed}{ipv4_change_str} IPv4 addresses changed; "
|
149 | 163 | f"{ipv6_changed}{ipv6_change_str} IPv6 addresses changed"
|
150 | 164 | )
|
| 165 | + elif args.subcommand == "diff_addrs": |
| 166 | + state1 = load_file(args.infile1) |
| 167 | + state2 = load_file(args.infile2) |
| 168 | + address_info = json.load(args.addrs_file) |
| 169 | + addrs = {a["address"] for a in address_info if a["network"] in ["ipv4", "ipv6"]} |
| 170 | + reassignments = defaultdict(list) |
| 171 | + for addr in addrs: |
| 172 | + net = ipaddress.ip_network(addr) |
| 173 | + prefix = asmap.net_to_prefix(net) |
| 174 | + old_asn = state1.lookup(prefix) |
| 175 | + new_asn = state2.lookup(prefix) |
| 176 | + if new_asn != old_asn: |
| 177 | + reassignments[(old_asn, new_asn)].append(addr) |
| 178 | + reassignments = sorted(reassignments.items(), key=lambda item: len(item[1]), reverse=True) |
| 179 | + num_reassignment_type = defaultdict(int) |
| 180 | + for (old_asn, new_asn), reassigned_addrs in reassignments: |
| 181 | + num_reassigned = len(reassigned_addrs) |
| 182 | + num_reassignment_type[(bool(old_asn), bool(new_asn))] += num_reassigned |
| 183 | + old_asn_str = f"AS{old_asn}" if old_asn else "unassigned" |
| 184 | + new_asn_str = f"AS{new_asn}" if new_asn else "unassigned" |
| 185 | + opt = ": " + ", ".join(reassigned_addrs) if args.show_addresses else "" |
| 186 | + print(f"{num_reassigned} address(es) reassigned from {old_asn_str} to {new_asn_str}{opt}") |
| 187 | + num_reassignments = sum(len(addrs) for _, addrs in reassignments) |
| 188 | + share = num_reassignments / len(addrs) if len(addrs) > 0 else 0 |
| 189 | + print(f"Summary: {num_reassignments:,} ({share:.2%}) of {len(addrs):,} addresses were reassigned " |
| 190 | + f"(migrations={num_reassignment_type[True, True]}, assignments={num_reassignment_type[False, True]}, " |
| 191 | + f"unassignments={num_reassignment_type[True, False]})") |
151 | 192 | else:
|
152 | 193 | parser.print_help()
|
153 | 194 | sys.exit("No command provided.")
|
|
0 commit comments