MikroTik RouterOS Automation with NAPALM

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

mikrotik enable api service

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.

NetScout LinkRunner G2

LinkRunner G2 is the ultimate network cable test tool


CAT5 Cable Tester, Measure Cable Length,
PoE Voltage, Network Connectivity, Switch Port ID
Optional Wireless & Fiber Optics Modules
Check Price on Amazon

pictory

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Ads Blocker Image Powered by Code Help Pro

πŸ™πŸ™A Humble Request to Disable AdBlock πŸ™πŸ™

You can close this message & continue reading but...
❀️❀️❀️ Please consider visiting one of my sponsors first ❀️❀️❀️

DigitalOcean πŸš€

Sign up and get a $200, 60-day credit to try DO.
Spend $25 after your credit expires and I will also get $25 in credit!
DigitalOcean Referral Badge

Pictory πŸ€–

Create amazing videos using Pictorys AI powered software.
Its FREE to create your first 3 video projects
pictory


Hi Reader, I noticed that you are using an ad blocker while visiting my website. While I completely understand that excessive ads can hinder your browsing experience, ad revenue helps pay for the cost associated with operating this website.

jcutrer.com is a labor of love, created with the primary aim to provide you with quality content, free of cost. It’s a space where I share information, ideas, and insights that I hope have a meaningful impact. However, maintaining and updating this platform incurs substantial costs.

Sincerely,
Jonathan