Automating Mikrotik provisioning, or how I learned to love IPv6 link-local addressing
In the realm of network management, automating device provisioning stands as a critical and often challenging task. Recently a customer asked me to help them in automating the provisioning of Mikrotik devices. For those not familiar, Mikrotik produces a wide range of network hardware - routers, switches, wifi access points, etc. In this case, the requirement was to take a Mikrotik router out of the box and have a configuration file pushed to it with zero user interaction.
For some background, I had already developed a robust provisioning / asset management system for this customer that generates router configuration files for devices based off templates, so there was already an internal RESTful API in place to get the target configuration file for a device based off it's serial number - the only missing piece was getting the configuration file to the device.
Traditionally, Mikrotik recommends using a utility called "Winbox" for device configuration. While functional, Winbox has its limitations: it requires Windows (or Wine on Linux) and lacks automation capabilities. The manual process of using Winbox is time-consuming and inefficient, especially when configuring multiple devices. In the case of this customer the staff had to download the configuration file to their local PC, and then run Winbox to discover the device and apply the configuration. As you can imagine, if you need to configure a large number of devices quickly this process can become quite cumbersome.
Previously for Cisco devices I'd achieved "out of the box" automation using a mixture of tftp boot scripts and EMM scripting, but Mikrotik devices don't have the same default behaviour as Cisco - by default, a Mikrotik device will come out of the box with a static IP on the first ethernet port of 192.168.88.1 and will not attempt to request an IP via DHCP. They do, however, have a very powerful REST based API that is enabled by default, and someone generated a very helpful web version of the schema, making it very simple to implement.
With connectivity to the device via the default IP, I quickly got a proof of concept provisioning script working that would do the following steps:
- Connect to the device and use the Mikrotik REST API to get the device information (model, serial number, etc)
- Call the customers internal API to get the devices configuration file
- Use SSH to push the configuration file to the device
- Call the API once again to apply the new configuration.
So I had somewhat automated provisioning, but this wasn't ideal - it still required someone to trigger the process and it only allowed for one device to be provisioned at a time since if there were two devices connected the IP addresses would conflict.
Putting my thinking cap on, I knew there had to be a solution to the problem. I went back to the beginning and looked at how the official Mikrotik process with Winbox worked. I discovered that Winbox uses a modified form of the Cisco Discovery Protocol (CDP) protocol called Mikrotik Network Discovery Protocol "mndp" to discover devices. Looking into how I might be able to use this, some further internet searching lead me MAC-Telnet project on Github, the work of Håkon Nessjøen. This project had an open source implementation of a daemon that listened for mndp packets and printed them to the console called "mndp". This is where I had my eureka moment - mndp would get me the MAC address of the device, and if I knew that, I could calculate the IPv6 link-local address for the device and connect to it.
For those who don't know, IPv6 link-local addresses are a unique kind of address in IPv6 networking, used exclusively for communication within the same network segment or "link." These addresses are non-routable, which means they are not intended for transmitting data over the internet or across different network segments. Instead, they are crucial for local communications within a Local Area Network (LAN).
A notable aspect of IPv6 link-local addresses is the way most network devices generate them. Typically, a device uses its MAC address, a hardware identifier unique to each network interface, as a basis for creating its IPv6 link-local address. This process involves inserting a specific set of bytes ('FFFE') into the middle of the MAC address and flipping a specific bit to indicate that it's a locally administered address. This method, known as Modified EUI-64 format, ensures that each device's IPv6 link-local address is unique on the local network. And as it turns out, Mikrotik devices follow this standard and generate their default IPv6 link-local address using the devices mac address.
Armed with this information, I just needed a way to get the information from the mndp daemon into some place I could use it to automate the provisioning process, so I modified the mndp application to send a message to a rabbitmq message queue when a new device was discovered on the network. With the data about discovered devices now in a queue, I wrote a PHP script that read messages from the queue, calculated the IPv6 link-local address of the device, and the executed the provisioning steps from my initial attempt. With this process in place I'd achieved my goal - I could now plug a device in, mndp would discover it and post the mac address to a queue, and then the php script would do the provisioning.
This automation significantly streamlined the provisioning process, saving time and reducing manual effort. It was a fun project and definitely took a lot of creative thought to put all of the pieces together. It was the first time I'd ever done any real work with IPv6 link-local addressing like this and definetly something I'll keep in my back pocket for future solutions.