World of networks and [Mikrotik](https://mikrotik.com) devices

Different DHCP for Windows/Linux Dual Boot

Illustration

While I am using Linux as my daily driver, I still need to use Windows from time to time. This means that I have both systems to back up. As I try to be a good boy, all my backup scripts are running on my server. However, due to how they are written, it’s rather hard to distinguish which system is running at what time. One way to solve this would be to rewrite the scripts to enable auto-detection, but a simpler alternative would be to assign different IP addresses to Windows and Linux thus leaving working scripts alone. Using a static IP could be a simple solution, but where’s the fun in that? I decided to make my Mikrotik router assign a different DHCP address depending on which system I boot.

When it comes to Mikrotik routers, the configuration options for delivering DHCP are quite rich but not very flexible when matching inputs. Yes, there is an option to recognize the OS (vendor-class), but this requires a different pool for those devices. If you want to match inputs without managing multiple pools, mac-address and client-id are only differentiators. Since I don’t want to mess with MAC address, choice is clear.

In Windows, it is hard to alter client identifier, so I decided not to alter its values at all. But Linux, as always, is a land of opportunity. So, to change the class identifier, it’s enough to just tell the network manager you want to do so. In the example below, I wanted to change the client identifier on a wireless network named Ursa Major:

nmcli con modify "Ursa Major" ipv4.dhcp-client-id "Ubuntu"

Alternatively, you can edit a file in /etc/netplan (the exact name varies per system; it was /etc/netplan/90-NM-c70925a8-cdca-491a-a6b3-db2263db425a.yaml for me). There, just add the ipv4.dhcp-client-id: "<whatever>" setting under the networkmanager key. My file looked something like this (some sections omitted):

network:
  version: 2
  wifis:
    NM-c70925a8-cdca-491a-a6b3-db2263db425a:
      access-points:
        "Ursa Major":
          networkmanager:
            uuid: "c70925a8-cdca-491a-a6b3-db2263db425a"
            name: "Ursa Major"
            passthrough:
              ipv4.dhcp-client-id: "Ubuntu"

Combine either of these changes with RouterOS client-id matching, and you have the same machine using different IPs, depending on the OS it booted in.

Powering Mikrotik Router from Netgear GS305EP

Illustration

Occasionally I find myself with piece of information that is unlikely to help anybody other than me in future. But that never prevented me from making a post before, so why should it now. In the left corner, we have my existing Mikrotik Audience router that yearns for some power via passive PoE. And in the right corner we have Netgear GS305EP PoE switch that’ll give its best using 802.3af/at. Can we make them play together?

Short answer is no - you cannot power router that uses passive PoE from a switch that provides only 802.3af/at. But… Depending on both the exact passive PoE device and how the active PoE switch is doing detection, you might get lucky and things will work.

First prerequisite is for client device to support 56V on PoE input. Anything lower than that and you’ll probably fry your device if you attempt powering it. Second prerequisite is that active PoE device supports legacy mode where resistors are used to determine power level. And lastly, your client device needs to have those resistors even though it doesn’t support active PoE as such. In Settings this would look like this:

Power Mode .....: Pre-802.3at
Power Limit Type: User
Power Limit (W) : 30.0
Detection Type .: 4pt 802.3af + Legacy

If you are lucky with all that, you might have power flowing between two officially incompatible devices.

Caveat emptor!

Mikrotik and ED25519 Keys

Well, it seems miracles do happen. According to the 7.9 testing release notes, Mikrotik will finally support ED25519 host keys. But, is this even important? I would argue yes.

First of all, ED25519 keys are MUCH shorter and significantly faster while providing higher security margin than 2048-bit RSA keys. If you want to use the same key to centrally manage your network and you have some underpowered clients, you will definitely feel RSA slowness when establishing connection - especially when dealing with high-ping situations. And shorter keys are not anything to frown upon either as they get much easier to copy/paste than wall of text RSA provides.

Secondly, security of ED25519 seems quite robust and sits somewhere between 2048-bit and 4096-bit RSA key. Unless there is a major breakthrough in cracking ED25519, this is good enough for foreseeable future. When/if quantum computers become a reality, both RSA and ED25519 are fcked so you’re in a losing battle. However, ED25519 keys seem to have a quantum-resistant NTRU-X25519 key exchange in OpenSSH while there is nothing similar for RSA.

Albeit I’m not cryptographer, I do listen to a lot of smart ones and most of them assume any quantum scaling breakthrough necessary to break ED25519 keys will buy a few years at most for RSA algorithm. In short, while both RSA and ED25519 may be doomed at undefined time in the future, it seems unnecessary to avoid faster algorithm ED25519 is today.

Lastly, for me this will mean I can use a single management key once more as, at this time, I’m using ED25519 for most of my needs with RSA being exclusively kept for the purpose of managing Mikrotik. Finally, I’ll be able to use one key to rule them all.

Good luck with upgrade!


[2023-05-04: Unfortunately, ED25519 support is partial at best. If you try to assign key to a user, you’ll get unable to load key file (wrong format or bad passphrase]

[2023-08-18: Well, while ED25519 support has been with us since 7.9, one couldn’t import any ED25519 keys. If 7.12 beta 1 release notes are to be believed (“ssh - added support for user ed25519 public keys”), we should finally have it done fully and properly. Let’s see…]

[2023-11-15: At last, ED25519 is supported by Mikrotik as of RouterOS 7.12]

Mikrotik Upgrade via SSH

New Mikrotik version came out and my firewall was just a bit too tight to allow remote WinBox connection. But I did have SSH…

And yes, upgrading to new version is easy enough from command line too. It’s just that one needs to execute two (you can omit the check) commands for the same GUI experience.

/system/package/update check-for-updates /system/package/update download /system/reboot

And that’s it, the new version is in.

Wireguard on Mikrotik RouterOS 7 (and an Ubuntu Client Setup)

With an upgrade to Mikrotuk RouterOS 7.2, my OpenVPN setup started showing signs of distress in the form of a connection loss every hour or so. Instead of downgrading to the previously good version, I decided to abandon OpenVPN altogether. I figured it was about time to get Wireguard going.

As with OpenVPN setup, I will show all steps assuming you’re comfortable with both RouterOS and Ubuntu command line. And yes, an Ubuntu setup will work pretty much for any other linux with just a few minot changes.

First we need to create a Wireguard interface on the Mikrotik router. Here make a note of the “SERVER-PUBLIC” key. You will need it later.

/interface wireguard
add listen-port=51820 name=wireguard1
print
 Flags: X - disabled; R - running``
 0  R name="wireguard1" listen-port=51820 private-key="^^SERVER-PRIVATE^^" public-key="^^SERVER-PUBLIC^^"``

With the interface created, we need to add IP address for it. In my case, I choose 192.168.2.1 in a completely separate 192.168.2.0/24 subnet for this purpose.

/ip address
add address=^^192.168.2.1/24^^ network=^^192.168.2.0^^ interface=wireguard1

Finally, assuming you have a firewall sorted out, we need to add two rules - one for Wireguard itself and another one to allow communication with other nodes connected to the same router. I will add both of them at the very beginning but you should adjust their location to fit with your setup.

/ip firewall filter
add chain=input protocol=udp dst-port=51820 action=accept place-before=0
add chain=forward in-interface=wireguard1 action=accept place-before=1

To allow Wireguard clients access to Internet, we also need to do some masquerade (assuming ether1 is your Internet interface).

/ip firewall nat
chain=srcnat src-address=^^192.168.2.0/24^^ out-interface=ether1 action=masquerade

Now we need to get onto Ubuntu client and set wireguard there. The first step is, of course, to install some packages.

sudo apt update
sudo apt install --yes wireguard

Before anything else, we need a private and public key created. I like to get them both into variables instead of the files. Yes, it’s not as secure but for a single-user computer it’s good enough. Do make note of client’s public key as we’ll need it soon.

WG_PRIVATE_KEY=`wg genkey`
WG_PUBLIC_KEY=`echo $WG_PRIVATE_KEY | wg pubkey`
echo $WG_PUBLIC_KEY
 CLIENT-PUBLIC

Now we need to create a Wireguard configuration file. Make sure to replace “SERVER-PUBLIC” with whatever public key you generated on server (not client!) and for endpoint make sure you give IP (or DNS name) of your router.

cat &lt;&lt; EOF | sudo tee /etc/wireguard/wg0.conf
[Interface]
PrivateKey = $WG_PRIVATE_KEY
Address = ^^192.168.2.20/24^^

[Peer]
PublicKey = ^^SERVER-PUBLIC^^
Endpoint = ^^192.168.0.1^^:51820
AllowedIPs = 0.0.0.0/0
EOF

Once we have the config file ready, we need to get back to RouterOS and add our client as a peer using its public key. Note that this “CLIENT-PUBLIC” is a public key we got in Ubuntu just a few moments ago.

/interface wireguard peers
add interface=wireguard1 allowed-address=^^192.168.2.20/24^^ public-key="^^CLIENT-PUBLIC^^"

If everything went fine, you should have VPN properly configured. The easiest way of checking it is to simply bring interface up and check the route. It should show us using Wireguard interface (and IP) with pings flowing freely.

sudo wg-quick up wg0

ip route get 1.1.1.1
 1.1.1.1 dev wg0 table 51820 src 192.168.2.20 uid 1000

ping 1.1.1.1

If we want this connection to be up every time we boot the system, we can enable it as a service

sudo systemctl enable wg-quick@wg0.service

And that’s all folks.

Mikrotik Configuration Backup

Backing up Mikrotik configuration is easy. Just save the backup and copy it to your machine.

ssh router.local '/export file=mybackup' $ scp router.local:/mybackup.rsc /backup/mybackup.rsc

This results in a binary file you can directly load upon restore. And that’s ok for the backup. But what if you want to track changes over time? Binary file won’t do. Welcome text export.

Fortunately, we can also get /export to output the data in textual format. Since this data will include things like dates and similar things that change with every execution, we use awk and sed to remove those.

ssh router.local '/export' \
  | tr -d '\r' \
  | awk '{sub(/^ +/, "", $0); if (sub(/\\$/,"")) printf "%s", $0; else print $0}' \
  | sed "s/^# .* by RouterOS/# RouterOS/" \
  | sed "/^# managed by CAPsMAN$/d" \
  | sed "/^# channel: .*, CAPsMAN forwarding$/d" \
  | sed "/^# $/d" \
  > /backup/mybackup.txt

The result is reasonably clean file that can be committed to Git or any version control software of your choice.

Getting IPv6 Going on RouterOS 7.1

I already did a post about IPv6 on Mikrotik but with RouterOS 7 going out, some things have slightly changed. So, it’s time for an updated guide. And, as one might expect, things are pretty much the same.

As before, prerequisite is that you get at least /64 prefix from your ISP (Comcast in my case) via DHCPv6. Also assumed is empty IPv6 configuration.

The first thing I like doing is disabling the default neighbor discovery interface. Blasting IPv6 router advertisements on all interfaces is not necessarily a good idea:

/ipv6 nd
set [ find default=yes ] disabled=yes

The next step is to setup DHCP client. Within a few seconds, you should see the prefix being allocated:

/ipv6 dhcp-client
add add-default-route=yes interface=ether1 pool-name=^^general-pool6^^ request=prefix  use-peer-dns=no

:delay 5s
print
 Flags: D - dynamic, X - disabled, I - invalid
  #    INTERFACE             STATUS        REQUEST             PREFIX
  0    ether1                bound         prefix              ^^2601:db8:9780:ee2c::/64^^, 3d14h41m41s

At this time I love to allocate address ending with ::1 to the router itself:

/ipv6 address
add address=::1 from-pool=^^general-pool6^^ interface=^^bridge1^^ advertise=yes

Now it should be possible to ping its address from external computer (in this example address would be 2601:db8:9780:ee2c::1). If this doesn’t work, do check if you have link-local addresses. If none are present, reboot the router and they will be regenerated.

With router reachable, it is time to delegate IPv6 prefix to internal machines too. For this purpose, setup RA (router announcement) over the bridge. While default interval settings are just fine, I like to make them a bit shorter (20-60 seconds):

/ipv6 nd
add interface=^^bridge1^^ ra-interval=20s-60s

And that’s all. Now your computers behind the router will have direct IPv6 route to the Internet. Do not forget to setup both router firewall and firewall of individual devices. There is no NAT to save your butt here.

PS: Here is the basic IPv6 firewall allowing all connections out while allowing only established back in:

/ipv6 firewall filter

add chain=input action=drop connection-state=invalid comment="Drop invalid"
add chain=input action=accept connection-state=established,related comment="Accept established"
add chain=input action=accept in-interface=ether1 protocol=udp src-port=547 limit=10,20:packet
add chain=input action=drop in-interface=ether1 protocol=udp src-port=547 comment="Drop ext DHCP >10/sec"
add chain=input action=accept in-interface=ether1 protocol=icmpv6 limit=10,20:packet
add chain=input action=drop in-interface=ether1 protocol=icmpv6 comment="Drop ext ICMP >10/sec"
add chain=input action=accept in-interface=!ether1 protocol=icmpv6 comment="Accept internal ICMP"
add chain=input action=drop in-interface=ether1 comment="Drop external"
add chain=input action=reject comment="Reject everything else"

add chain=output action=accept comment="Accept all"

add chain=forward action=drop connection-state=invalid comment="Drop invalid"
add chain=forward action=accept connection-state=established,related comment="Accept established"
add chain=forward action=accept in-interface=ether1 protocol=icmpv6 limit=20,50:packet"
add chain=forward action=drop in-interface=ether1 protocol=icmpv6 comment="Drop ext ICMP >20/sec"
add chain=forward action=accept in-interface=!ether1 comment="Accept internal"
add chain=forward action=accept out-interface=ether1 comment="Accept outgoing"
add chain=forward action=drop in-interface=ether1 comment="Drop external"
add chain=forward action=reject comment="Reject everything else"

Local WAN Redirect on Mikrotik

As a keeper of the family Minecraft server, I had the holy duty of setting up the firewall to accept it from the outside. External DNS was as easy as CNAME toward the router cloud name. Sorting out redirects was equally uneventful. Just poke a few holes in the firewall so it plays nice with NAT and you’re essentially done:

/ip firewall filter
add action=accept chain=input protocol=tcp dst-port=25565 \
    comment="Accept Minecraft"
add action=accept chain=forward in-interface=ether1 protocol=tcp dst-port=25565 \
    comment="Accept Minecraft (Internet)"

/ip firewall nat
add action=dst-nat chain=dstnat in-interface=ether1 protocol=tcp dst-port=25565 \
    to-addresses=192.168.1.4 to-ports=25565 comment="Minecraft WAN Redirect" 

In order to access it from the internal network, I just added entry in my DNS server with the local address and life was good.

However, with the move toward encrypted DNS, my Mikrotik wasn’t the main source of truth anymore. Suddenly computers on my network would receive external IP and my router didn’t know how to route that. Since routing is one of its duties, you can see why I would have a problem with this.

Most solutions I’ve seen dealt with hairpin NAT but one didn’t. And my solution was essentially the same:

/ip firewall address-list
add address=10.0.0.0/8 list=local-list
add address=172.16.0.0/12 list=local-list
add address=192.168.0.0/16 list=local-list

/ip firewall nat
add action=dst-nat chain=dstnat protocol=tcp dst-port=25565 \
    dst-address-list=!local-list dst-address-type=local \
    to-addresses=192.168.1.4 to-ports=25565 comment="Minecraft Local WAN Redirect" 

This essentially says to redirect any address local to router (and WAN address is present on router even in DHCP case) but not present in the list of local addresses (i.e. globally routable) to our internal IP and port. As simple as it gets.

UDP OpenVPN on Mikrotik 7

Illustration

Despite UDP being ubiquitous on pretty much any other OpenVPN platform, for a long while Mikrotik only supported TCP variant. With Mikrotik RouterOS 7 finally being released earlier this year, we at last got an UDP support for OpenVPN.

For some people UDP/TCP difference might not matter much. If you have a stable connection chances are you really don’t need to care - OpenVPN via TCP will serve you without any issues.

But, if you are dealing with multiple connections over a high latency and/or lossy network, UDP will be much faster as lost packets for one connection will not impact the other. How big the difference is? Well, I have a connection between USA and Croatia and it leaks like a sieve. My speed went from about 400 Kbps to 1000 Kbps just due to this change (tested using 2 parallel connections). I would say switching to UDP was well worth the effort for my use case.

Getting UDP enabled for OpenVPN server once you get Mikrotik 7.1 or higher running is trivial assuming you have OpenVPN via TCP already configured. You just change Protocol value to udp, update your client side with the same change (albeit for proto field) and you’re done.

But, in the interest of completeness, let’s see how one would create such config from scratch.

First we create all the certificate templates (give it at least 10 years validity):

/certificate
add name=ca-template common-name=^^example.com^^ days-valid=3650 \
  key-size=2048 key-usage=crl-sign,key-cert-sign
add name=server-template common-name=^^*.example.com^^ days-valid=3650 \
  key-size=2048 key-usage=digital-signature,key-encipherment,tls-server
add name=client-template common-name=^^client.example.com^^ days-valid=3650 \
  key-size=2048 key-usage=tls-client

As far as OpenVPN server is concerned, you can use whatever you want for certificate’s common name. Since some other VPNs are not as forgiving (yes SSTP, I am looking at you), I made it a habit to use either external IP or the host name here.

Once we have templates sorted out, we can do the signing:

/certificate
sign ca-template name=ca-certificate
sign server-template name=server-certificate ca=ca-certificate
sign client-template name=client-certificate ca=ca-certificate

And then exporting certificate material:

/certificate
export-certificate ca-certificate export-passphrase=""
export-certificate client-certificate export-passphrase=^^12345678^^

This should give you three files: cert_export_ca-certificate.crt, cert_export_client-certificate.crt, and cert_export_client-certificate.key. After copying these files to the computer for later I like to rename them to ca.crt, client.crt, and client.key respectively. It just makes everything a bit tidier.

Next we need a separate pool of IP addresses for clients. I will assume you have your clients in some other network (e.g. 192.168.1.x) and this new network is just for VPN:

/ip
pool add name="vpn-pool" ranges=192.168.8.10-192.168.8.99

Instead of editing the default encrypted profile, we can create a new one. If you use different DNS server, do change it here, and while at it, you should really use a bit more imaginative user/password pair:

/ppp
profile add name="vpn-profile" use-encryption=yes idle-timeout=10m \
  local-address=192.168.8.250 dns-server=1.1.1.1 remote-address=vpn-pool \
  secret add name=^^user^^ profile=vpn-profile password=^^password^^

Finally, we can enable OpenVPN server interface:

/interface ovpn-server server
  set default-profile=vpn-profile certificate=server-certificate require-client-certificate=yes \
  auth=sha1 cipher=aes128,aes192,aes256 enabled=yes protocol=udp

Assuming you’re using Windows, you can copy both ca.crt and client.crt to C:\Program Files\OpenVPN\config\ directory alongside client.ovpn. On Linux, one would do the same, just in the /etc/openvpn/client directory.

You don’t have client.ovpn? Well, one is in sample-config directory and we just need to change/add the highlighted items. And since we’re finally using UDP, we can leave proto as it is.

client
dev tun
proto udp
remote ^^example.com^^ 1194
resolv-retry infinite
nobind
persist-key
persist-tun
ca ca.crt
cert client.crt
key client.key
remote-cert-tls server
cipher AES-128-CBC
auth SHA1
auth-user-pass
redirect-gateway def1
verb 3

A bit annoying step is being asked for the private key passphrase (in the addition to the username/password pair). Mikrotik doesn’t allow export without it but fortunately we can use OpenSSL to change that:

openssl.exe rsa -in client.key -out client.key
 Enter pass phrase for client.key: 12345678
 writing RSA key

With this, your VPN connection should work like a charm.

PS: Do not forget to adjust firewall if necessary (TCP port 1194).

/ip firewall filter
add chain=input protocol=udp dst-port=1194 action=accept place-before=0 comment="Allow OpenVPN"

DNS over HTTPS for Mikrotik

Illustration

With everything moving to HTTPS, there’s still one component that gets overlooked - DNS. Most of time lookups are still done via essentially plain-text protocol. And it’s not for the lack of encrypted alternatives as there are at least three different ways of doing it: DNS over HTTPS (DoH), DNS over TLS (DoT), and DNSCrypt. When it comes to Mikrotik, choice narrows a bit and only DNS over HTTPS is supported.

Realistically, DoH is enough. DoT might be a bit more elegant implementation at a lower OSI layer but both clients and public servers seem to prefer DoH due to it’s heavy reliance on HTTPS layer ubiquitous to pretty much any application these days. DNSCrypt never got off the ground and we pretty much can ignore it.

So, today’s task is to set Mikrotik router to use DNS over HTTPS lookups.

To start it all off, we first need to download CA certificate store. And yes, we could skip this step and simply not check certificates but I feel that leaves too much space for man-in-the-middle attach. Downloading one file every few years will not kill anybody.

/tool fetch url=https://curl.se/ca/cacert.pem
      status: finished
       total: 199KiB
    duration: 1s

/certificate import file-name=cacert.pem passphrase=""
     certificates-imported: 128``
     private-keys-imported: 0
            files-imported: 1
       decryption-failures: 0
  keys-with-no-certificate: 0

With certificates in place, we get to setup DNS over HTTPs.

/ip dns
set use-doh-server=^^https://cloudflare-dns.com/dns-query^^ verify-doh-cert=yes

And that’s it. To verify it working, you can visit https://1.1.1.1/help and look for “Using DNS over HTTPS (DoH)” line.


That said, one could notice one glaring issue - we still have our original DNS servers listed. And I personally do this intentionally as a fallback method. However, that does leak a bit of information and, if someone blocks our DNS resolver, can force plain-text legacy lookups.

To harden system a bit, first we need to add static entries for DNS IPs (you can find them out with nslookup cloudflare-dns.com).

/ip dns static
add name=cloudflare-dns.com address=^^2606:4700::6810:f8f9^^
add name=cloudflare-dns.com address=^^2606:4700::6810:f9f9^^
add name=cloudflare-dns.com address=^^104.16.248.249^^
add name=cloudflare-dns.com address=^^104.16.249.249^^

With static entries in place, we can clear the legacy DNS servers.

/ip dns
set servers=""

And, if we’re using DHCP, we need to disregard DNS IPs provided by upstream too.

/ip dhcp-client
set 0 use-peer-dns=no

/ipv6 dhcp-client
set 0 use-peer-dns=no

And now all DNS lookups done by router are done using DoH and nothing else.