MikroTik RouterOS Automation with NAPALM
In this tutorial, we will explore using the NAPALM python module to query data from a MikroTik Router.
Before we begin, you are expected to have python3 and pip installed as well as access to a MikroTik router running RouterOS. NAPALM will attempt to connect to the router on the default API port of 8728. You will need to enable the API service which is found in IP | Services
using winbox
This tutorial was developed as a Jupyter Notebook, it’s available on github if you want to download it and follow along. Download the Notebook
At the time of writing this article, the RouterOS NAPALM driver does not support configuration management for MikroTik routers. For now, we will look at what information can be read from the router.
DigitalOcean offers one-click deployment of popular applications such as WordPress, Django, MongoDB, Docker, and even preconfigured Kubernetes Clusters. Deploy your next app in seconds. Get $100 in cloud credits from DigitalOcean
Ad Notice I will receive a small commission that helps support this blog at no cost to you.
Here are the functions that NAPALM-ROS currently supports
According to the napalm-ros github page
get_arp_table
get_interfaces_counters
get_environment
get_facts
get_interfaces
get_interfaces_ip
get_ntp_servers
get_snmp_information
get_users
get_ipv6_neighbors_table
is_alive
ping
Create a new pipenv virtualenv and install napalm & napalm-ros
mkdir napalmtest cd napalmtest pipenv --python 3.6 pipenv install napalm pipenv install napalm-ros
… or install with pip
If you have not idea what `pipenv` is, checkout my pipenv tutorial or alternatively you can install napalm
& napalm-ros
globally with pip
.
pip install napalm
pip install napalm-ros
Import NAPALM and the RouterOS driver
import napalm from napalm_ros import ros
Provide your MikroTik Router’s IP and credentials
router_ip = '192.168.1.1' router_port = 8728 # Use 8729 for api-ssl router_user = 'admin' router_pass = '<your-router-password>'
Configure the RouterOS driver then initialize and connect to the router
# Use the RouterOS (ros) network driver to connect to the device: driver = napalm.get_network_driver('ros') print('Connecting to', router_ip, "on port", router_port, "as", router_user) device = driver(hostname=router_ip, username=router_user, password=router_pass, optional_args={'port': router_port}) print('Opening ...') device.open()
Connecting to 192.168.1.1 on port 8728 as admin Opening ...
List available methods
method_list = [func for func in dir(device) if callable(getattr(device, func)) and not func.startswith("_")] for method in method_list: print(f"device.{method}()")
# Output device.api() device.cli() device.close() device.commit_config() device.compare_config() device.compliance_report() device.connection_tests() device.discard_config() device.get_arp_table() device.get_bgp_config() device.get_bgp_neighbors() device.get_bgp_neighbors_detail() device.get_config() device.get_environment() device.get_facts() device.get_firewall_policies() device.get_interfaces() device.get_interfaces_counters() device.get_interfaces_ip() device.get_ipv6_neighbors_table() device.get_lldp_neighbors() device.get_lldp_neighbors_detail() device.get_mac_address_table() device.get_network_instances() device.get_ntp_peers() device.get_ntp_servers() device.get_ntp_stats() device.get_optics() device.get_probes_config() device.get_probes_results() device.get_route_to() device.get_snmp_information() device.get_users() device.is_alive() device.load_merge_candidate() device.load_replace_candidate() device.load_template() device.open() device.ping() device.post_connection_tests() device.pre_connection_tests() device.rollback() device.traceroute()
As previously mentioned, some of these method such as .get_config()
are not implemented (yet).
To begin, let’s see if we are connected to the router?
print(device.is_alive())
{'is_alive': True}
Ping from the router
To be clear, the ping()
function performs an icmp ping from the router to some destination IP, not from your computer to the router.
print(device.ping('8.8.8.8'))
{'success': {'probes_sent': 5, 'packet_loss': 0, 'rtt_min': 50.0, 'rtt_max': 69.0, 'rtt_avg': 54.0, 'rtt_stddev': -1.0, 'results': [{'ip_address': '8.8.8.8', 'rtt': 69.0}, {'ip_address': '8.8.8.8', 'rtt': 51.0}, {'ip_address': '8.8.8.8', 'rtt': 50.0}, {'ip_address': '8.8.8.8', 'rtt': 50.0}, {'ip_address': '8.8.8.8', 'rtt': 51.0}]}}
A more advanced ping example with additional arguments
print(device.ping(destination='8.8.8.8', source='192.168.1.1', ttl=255, timeout=1000, size=64, count=3))
{'success': {'probes_sent': 3, 'packet_loss': 0, 'rtt_min': 50.0, 'rtt_max': 52.0, 'rtt_avg': 51.0, 'rtt_stddev': -1.0, 'results': [{'ip_address': '8.8.8.8', 'rtt': 52.0}, {'ip_address': '8.8.8.8', 'rtt': 51.0}, {'ip_address': '8.8.8.8', 'rtt': 50.0}]}}
Get SNMP Configuration
print(device.get_snmp_information())
{'chassis_id': '', 'community': {'public': {'acl': '0.0.0.0/0', 'mode': 'ro'}}, 'contact': '', 'location': ''}
Dump the Router’s Facts
print(device.get_facts())
{'uptime': 121823, 'vendor': 'MikroTik', 'model': 'RB951G-2HnD', 'hostname': 'R1', 'fqdn': '', 'os_version': '6.41.1 (stable)', 'serial_number': '3XXE021XXXXX', 'interface_list': ['bridge', 'ether1', 'ether2', 'ether3', 'ether4', 'ether5', 'wlan1', 'wlan2']}
Iterate over the Router Facts and print them
print("Facts about", router_ip) for key, value in device.get_facts().items(): print( f"{key}: {value}" )
Facts about 192.168.1.1 uptime: 124775 vendor: MikroTik model: RB951G-2HnD hostname: R1 fqdn: os_version: 6.41.1 (stable) serial_number: 3XXE021XXXXX interface_list: ['bridge', 'ether1', 'ether2', 'ether3', 'ether4', 'ether5', 'wlan1', 'wlan2']
Iterate over the router facts interface list
print("List of interfaces on this router") for interf in device.get_facts()['interface_list']: print( interf )
List of interfaces on this router bridge ether1 ether2 ether3 ether4 ether5 wlan1 wlan2
Query the router’s interface counter statistics
iface_stats = device.get_interfaces_counters()
Dump the Interfaces Stats
print(iface_stats)
{'bridge': defaultdict(int, {'rx_broadcast_packets': 0, 'rx_discards': 0, 'rx_errors': 0, 'rx_multicast_packets': 0, 'rx_octets': 1250539661, 'rx_unicast_packets': 6170632, 'tx_broadcast_packets': 0, 'tx_discards': 0, 'tx_errors': 0, 'tx_multicast_packets': 0, 'tx_octets': 19929132599, 'tx_unicast_packets': 14415771}), 'ether1': defaultdict(int, {'rx_broadcast_packets': 0, 'rx_discards': 0, 'rx_errors': 0, 'rx_multicast_packets': 0, 'rx_octets': 19983333109, 'rx_unicast_packets': 14369741, 'tx_broadcast_packets': 0, 'tx_discards': 0, 'tx_errors': 0, 'tx_multicast_packets': 0, 'tx_octets': 1252233599, 'tx_unicast_packets': 6028405}), ...output truncated... 'wlan1': defaultdict(int, {'rx_broadcast_packets': 0, 'rx_discards': 0, 'rx_errors': 0, 'rx_multicast_packets': 0, 'rx_octets': 0, 'rx_unicast_packets': 0, 'tx_broadcast_packets': 0, 'tx_discards': 0, 'tx_errors': 0, 'tx_multicast_packets': 0, 'tx_octets': 0, 'tx_unicast_packets': 0}), 'wlan2': defaultdict(int, {'rx_broadcast_packets': 0, 'rx_discards': 0, 'rx_errors': 0, 'rx_multicast_packets': 0, 'rx_octets': 535541903, 'rx_unicast_packets': 4070323, 'tx_broadcast_packets': 0, 'tx_discards': 0, 'tx_errors': 0, 'tx_multicast_packets': 0, 'tx_octets': 14832924157, 'tx_unicast_packets': 10469485})} <
Dump a single interface’s stats
print( iface_stats['ether1'])
defaultdict(<class 'int'>, {'tx_errors': 0, 'rx_errors': 0, 'tx_discards': 0, 'rx_discards': 0, 'tx_octets': 1252233599, 'rx_octets': 19983333109, 'tx_unicast_packets': 6028405, 'rx_unicast_packets': 14369741, 'tx_multicast_packets': 0, 'rx_multicast_packets': 0, 'tx_broadcast_packets': 0, 'rx_broadcast_packets': 0})
Iterate over an interface’s stats and print them
print( "ether1 Interface Stats") print("======================") for stat in iface_stats['ether1']: print(f"{stat}: {iface_stats['ether1'][stat]}")
ether1 Interface Stats ====================== tx_errors: 0 rx_errors: 0 tx_discards: 0 rx_discards: 0 tx_octets: 1252233599 rx_octets: 19983333109 tx_unicast_packets: 6028405 rx_unicast_packets: 14369741 tx_multicast_packets: 0 rx_multicast_packets: 0 tx_broadcast_packets: 0 rx_broadcast_packets: 0
List users
users = device.get_users() print(users)
{'admin': {'level': 15, 'password': '', 'sshkeys': []}}
Get all ARP Entries
arptable = device.get_arp_table() for entry in arptable: print(entry)
{'interface': 'bridge', 'mac': 'XX:00:0B:XX:XX:XX', 'ip': '192.168.1.3', 'age': -1.0} {'interface': 'bridge', 'mac': 'XX:9F:C2:XX:XX:XX', 'ip': '192.168.1.4', 'age': -1.0} {'interface': 'bridge', 'mac': 'XX:7C:9C:XX:XX:XX', 'ip': '192.168.1.5', 'age': -1.0} ...output truncated...
Get Interface Information
ifaces = device.get_interfaces() print("Print a list of interfaces with w/comments & status") for iface,data in ifaces.items(): print(f";;; {data['description']}") print(f"{iface} [enabled={data['is_enabled']}, up={data['is_up']}]") print()
Print a list of interfaces with w/comments & status ;;; WAN ether1 [enabled=True, up=True] ;;; Uplink to DVR ether2 [enabled=True, up=True] ;;; ether3 [enabled=False, up=False] ;;; uplink to NAS ether4 [enabled=True, up=True] ;;; UniFi AP ether5 [enabled=True, up=True] ;;; wlan1 [enabled=True, up=False] ;;; wlan2 [enabled=True, up=True] ;;; defconf bridge [enabled=True, up=True]
Gracefully disconnect from the Router
device.close()
References
Amazon AWS too complex and expensive? You will love the simplicity of DigitalOcean. Deploy your next app in seconds. Get $100 in cloud credits from DigitalOcean
Ad Notice I will receive a small commission that helps support this blog at no cost to you.
I hope you have enjoyed this python tutorial about NAPALM and RouterOS Automation. Checkout my other MikroTik tutorials and Python tutorials.
3 Replies to “MikroTik RouterOS Automation with NAPALM”
Thank you very much for an awesome tutorial.
I am glad that you found it useful, cheers!
Thanks, this is the best information I’ve found for automating RouterOS using Python!