Created
March 2, 2026 18:36
-
-
Save Strykar/8544127dde6bcc4fa77536b3e72609e5 to your computer and use it in GitHub Desktop.
RouterOS - MKTXP WAN detection helpers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Standalone RouterOS WAN topology inspector | |
| - Deterministic WAN derivation | |
| """ | |
| from routeros_api import RouterOsApiPool | |
| import ipaddress | |
| # ---- USER CONFIG ---- | |
| HOST = "192.168.88.1" | |
| USER = "mktxp_user" | |
| PASSWORD = "mktxp_pass" | |
| PORT = 8729 | |
| # -------------------- | |
| def main(): | |
| pool = RouterOsApiPool( | |
| HOST, | |
| username=USER, | |
| password=PASSWORD, | |
| port=PORT, | |
| use_ssl=True, | |
| plaintext_login=True, | |
| ssl_verify=False, | |
| ssl_verify_hostname=False, | |
| ) | |
| api = pool.get_api() | |
| # ROUTES | |
| v4_routes = api.get_resource("/ip/route").get(dst_address="0.0.0.0/0") | |
| v6_routes = api.get_resource("/ipv6/route").get(dst_address="::/0") | |
| print("\n=== MikroTik WAN routing (raw) ===\n") | |
| print("[IPv4]") | |
| for r in v4_routes: | |
| gw = r.get("gateway") | |
| igw = r.get("immediate-gw") | |
| if gw and igw and "%" in igw: | |
| print(f"{gw} -> {igw}") | |
| print("\n[IPv6]") | |
| for r in v6_routes: | |
| gw = r.get("gateway") | |
| igw = r.get("immediate-gw") | |
| if gw and "%" in gw: | |
| print(f"{gw} -> {igw}") | |
| # WAN INTERFACES (from default routes) | |
| def wan_iface_from_route(r): | |
| if "vrf-interface" in r: | |
| return r["vrf-interface"] | |
| igw = r.get("immediate-gw", "") | |
| if "%" in igw: | |
| return igw.split("%", 1)[1] | |
| return None | |
| wan_ifaces_v4 = {wan_iface_from_route(r) for r in v4_routes if wan_iface_from_route(r)} | |
| wan_ifaces_v6 = {wan_iface_from_route(r) for r in v6_routes if wan_iface_from_route(r)} | |
| wan_ifaces = wan_ifaces_v4 | wan_ifaces_v6 | |
| # IPv4 ADDRESSES | |
| ipv4_addrs = api.get_resource("/ip/address").get() | |
| ipv4_by_iface = {} | |
| for a in ipv4_addrs: | |
| iface = a.get("interface") | |
| ip = a.get("address", "").split("/")[0] | |
| if iface in wan_ifaces: | |
| ipv4_by_iface.setdefault(iface, []).append(ip) | |
| # IPv6: DYNAMIC POOLS ONLY | |
| pools = api.get_resource("/ipv6/pool").get() | |
| dynamic_prefixes = [] | |
| for p in pools: | |
| if p.get("dynamic") == "true": | |
| dynamic_prefixes.append(ipaddress.ip_network(p["prefix"], strict=False)) | |
| ipv6_addrs = api.get_resource("/ipv6/address").get() | |
| derived_ipv6 = {} # iface -> single IPv6 address | |
| for a in ipv6_addrs: | |
| addr = a.get("address") | |
| if not addr: | |
| continue | |
| ip = ipaddress.ip_address(addr.split("/")[0]) | |
| # Skip link-local | |
| if ip.is_link_local: | |
| continue | |
| # Must belong to a dynamic delegated prefix | |
| if not any(ip in pfx for pfx in dynamic_prefixes): | |
| continue | |
| # Map back to WAN via IPv6 default route interface | |
| for r in v6_routes: | |
| iface = wan_iface_from_route(r) | |
| if iface: | |
| derived_ipv6[iface] = str(ip) | |
| # OUTPUT | |
| print("\n=== Derived WAN interfaces & global addresses ===\n") | |
| for iface in sorted(wan_ifaces): | |
| for ip in ipv4_by_iface.get(iface, []): | |
| print(f"* {iface:<10} {ip}") | |
| if iface in derived_ipv6: | |
| print(f"* {iface:<10} {derived_ipv6[iface]}") | |
| pool.disconnect() | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| RouterOS WAN structure inspection script. | |
| """ | |
| from routeros_api import RouterOsApiPool | |
| # ---- USER CONFIG ---- | |
| HOST = "192.168.88.1" | |
| USER = "mktxp_user" | |
| PASSWORD = "mktxp_pass" | |
| PORT = 8729 | |
| # -------------------- | |
| def print_section(title): | |
| print("\n" + "=" * len(title)) | |
| print(title) | |
| print("=" * len(title)) | |
| def main(): | |
| pool = RouterOsApiPool( | |
| HOST, | |
| username=USER, | |
| password=PASSWORD, | |
| port=PORT, | |
| use_ssl=True, | |
| plaintext_login=True, | |
| ssl_verify=False, | |
| ssl_verify_hostname=False, | |
| ) | |
| api = pool.get_api() | |
| print_section("INTERFACES (/interface)") | |
| for i in api.get_resource("/interface").get(): | |
| print(f"{i.get('name'):15} type={i.get('type')}") | |
| print_section("IPv4 DEFAULT ROUTES (/ip/route dst=0.0.0.0/0)") | |
| for r in api.get_resource("/ip/route").get(**{"dst-address": "0.0.0.0/0"}): | |
| print(r) | |
| print_section("IPv6 DEFAULT ROUTES (/ipv6/route dst=::/0)") | |
| for r in api.get_resource("/ipv6/route").get(**{"dst-address": "::/0"}): | |
| print(r) | |
| print_section("IPv4 ADDRESSES (/ip/address)") | |
| for a in api.get_resource("/ip/address").get(): | |
| print(f"{a.get('interface'):15} {a.get('address')}") | |
| print_section("IPv6 ADDRESSES (/ipv6/address)") | |
| for a in api.get_resource("/ipv6/address").get(): | |
| print( | |
| f"{a.get('interface'):15} " | |
| f"{a.get('address'):35} " | |
| f"link-local={a.get('link-local')}" | |
| ) | |
| print_section("IPv6 POOLS (/ipv6/pool)") | |
| for p in api.get_resource("/ipv6/pool").get(): | |
| print(p) | |
| pool.disconnect() | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment