#!/usr/bin/env python3 """ Get all network devices from a MikroTik router (DHCP, ARP, interfaces) to identify access points and other devices """ import ssl import sys import json import argparse from collections import defaultdict try: from librouteros import connect except ImportError: print("Error: librouteros module not found") print("Install with: pip install librouteros") sys.exit(1) def connect_to_router(ip, username, password, use_ssl=True): """Connect to MikroTik router""" port = 8729 if use_ssl else 8728 try: if use_ssl: ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE api = connect( username=username, password=password, host=ip, port=port, ssl_wrapper=ctx.wrap_socket ) else: api = connect( username=username, password=password, host=ip, port=port ) print(f"✓ Connected to {ip}") return api except Exception as e: print(f"✗ Connection failed: {e}") return None def get_dhcp_leases(api): """Get DHCP leases""" try: return list(api.path('/ip/dhcp-server/lease')) except Exception as e: print(f"Error getting DHCP leases: {e}") return [] def get_arp_table(api): """Get ARP table""" try: return list(api.path('/ip/arp')) except Exception as e: print(f"Error getting ARP table: {e}") return [] def get_device_type(mac, hostname, comment): """Determine device type based on MAC prefix and other identifiers""" if not mac: return "Unknown" mac_upper = mac.upper() # Ubiquiti prefixes ubiquiti_prefixes = ['04:18:D6', 'F0:9F:C2', '24:A4:3C', '68:72:51', '80:2A:A8', 'FC:EC:DA'] if any(mac_upper.startswith(prefix) for prefix in ubiquiti_prefixes): # Check if it's likely an access point vs customer device if hostname or comment: name = (hostname or comment).lower() if any(x in name for x in ['ap', 'access', 'bridge', 'station', 'loco', 'nano', 'powerbeam', 'rocket']): return "Ubiquiti AP" return "Ubiquiti Device" # MikroTik prefixes mikrotik_prefixes = ['00:0C:42', '4C:5E:0C', '6C:3B:6B', 'D4:CA:6D', 'E4:8D:8C', 'DC:2C:6E', 'B8:69:F4', '48:8F:5A'] if any(mac_upper.startswith(prefix) for prefix in mikrotik_prefixes): if hostname or comment: name = (hostname or comment).lower() if any(x in name for x in ['ap', 'cap', 'hap', 'wap', 'sxt', 'lhg']): return "MikroTik AP" return "MikroTik Device" return "Other Device" def main(): parser = argparse.ArgumentParser(description='Get all network devices from MikroTik router') parser.add_argument('router_ip', help='Router IP address') parser.add_argument('-u', '--username', default='grahamro', help='Username (default: grahamro)') parser.add_argument('-p', '--password', default='cFKhz8q5gPLoucMbcT1Iy58r3IXgc3', help='Password') parser.add_argument('-o', '--output', help='Output file (JSON)') parser.add_argument('--no-ssl', action='store_true', help='Use plain API instead of API-SSL') parser.add_argument('--show-all', action='store_true', help='Show all devices, not just likely APs') args = parser.parse_args() api = connect_to_router(args.router_ip, args.username, args.password, not args.no_ssl) if not api: sys.exit(1) print("\n=== Retrieving Network Device Information ===\n") dhcp_leases = get_dhcp_leases(api) arp_table = get_arp_table(api) # Merge data by MAC address devices = defaultdict(lambda: { 'ip': '', 'mac': '', 'hostname': '', 'comment': '', 'status': '', 'type': 'Unknown', 'server': '', 'interface': '' }) # Process DHCP leases for lease in dhcp_leases: mac = lease.get('mac-address', '') if mac: dev = devices[mac] dev['mac'] = mac dev['ip'] = lease.get('address', dev['ip']) dev['hostname'] = lease.get('host-name', dev['hostname']) dev['comment'] = lease.get('comment', dev['comment']) dev['status'] = lease.get('status', dev['status']) dev['server'] = lease.get('server', dev['server']) # Process ARP table for arp in arp_table: mac = arp.get('mac-address', '') if mac: dev = devices[mac] dev['mac'] = mac if not dev['ip']: dev['ip'] = arp.get('address', '') if not dev['interface']: dev['interface'] = arp.get('interface', '') # Determine device types for mac, dev in devices.items(): dev['type'] = get_device_type(mac, dev['hostname'], dev['comment']) # Group by type by_type = defaultdict(list) for dev in devices.values(): by_type[dev['type']].append(dev) # Display results print("=== Network Devices by Type ===\n") for device_type in ['Ubiquiti AP', 'MikroTik AP', 'Ubiquiti Device', 'MikroTik Device', 'Other Device']: if device_type in by_type: devices_of_type = sorted(by_type[device_type], key=lambda x: x['ip']) if not args.show_all and device_type == 'Other Device': print(f"\n{device_type}s: {len(devices_of_type)} (use --show-all to display)") continue print(f"\n{device_type}s: {len(devices_of_type)}") print("-" * 100) for dev in devices_of_type: name = dev['hostname'] or dev['comment'] or dev['mac'] print(f" {name:<35} {dev['ip']:<15} {dev['mac']:<17} {dev['interface']:<20}") if dev['comment'] and dev['comment'] != name: print(f" Comment: {dev['comment']}") # Summary print(f"\n=== Summary ===") print(f"Total devices found: {len(devices)}") for device_type, devs in sorted(by_type.items()): print(f" {device_type}: {len(devs)}") # Save to file if requested if args.output: output_data = { 'router': args.router_ip, 'devices': [dev for dev in devices.values()], 'by_type': {k: v for k, v in by_type.items()} } with open(args.output, 'w') as f: json.dump(output_data, f, indent=2) print(f"\nData saved to: {args.output}") api.close() if __name__ == '__main__': main()