Skip to content

Instantly share code, notes, and snippets.

@maurice-w
Last active March 17, 2026 09:41
Show Gist options
  • Select an option

  • Save maurice-w/402eea6750738c7a6765219c34260283 to your computer and use it in GitHub Desktop.

Select an option

Save maurice-w/402eea6750738c7a6765219c34260283 to your computer and use it in GitHub Desktop.
MikroTik RouterOS PPPoE Offloader

MikroTik RouterOS PPPoE Offloading

This example setup for RouterOS 7.22 configures a MikroTik router as a PPPoE offloader. The router establishes a connection to a PPPoE server (e. g. over GPON or DSL), acquires an IPv4 address and an IPv6 prefix and delegates these to a downstream firewall via DHCP. The MikroTik device acts purely as a router, it has no firewall / NAT rules.

This is motivated by wanting a basic IP over Ethernet connection for the WAN interface of the main firewall (e. g. OPNsense). Specific hardware and protocols required for the WAN uplink are handled by the MikroTik device. RouterOS is known to have a robust PPPoE implementation.

It is assumed that the router has no default configuration like bridges, firewall rules etc.:

/system/reset-configuration no-defaults=yes

Interfaces used in this example:

  • sfp1 is the upstream interface (used for establishing the PPPoE connection)
  • ether1 is the downstream interface (connects to the WAN interface of the firewall)
  • ether2 is a dedicated management interface

You can of course use bridges, VLANs etc. instead.

I currently (2025 / 2026) use this in production with a hEX S (RB760iGS) which is equipped with a GPON ONT SFP.

Configure an isolated management interface

/ip/vrf/add interfaces=ether2 name=management

Configure the management interface layer 3 settings to your liking (IP addresses, routes, ...).

Disable unneeded services and restrict required ones to the management VRF

We don't want any RouterOS services exposed to the WAN.

/ip/neighbor/discovery-settings/set discover-interface-list=none
/ip/service
set ftp disabled=yes
set ssh vrf=management
set telnet disabled=yes
[...]

Configure the downstream interface

Use dummy addresses for now, these will later be set dynamically.

/ip/address/add interface=ether1 address=203.0.113.26/30
/ipv6/address/add interface=ether1 address=2001:db8::1

SLAAC doesn't work particularly well with some firewalls, so don't set the A flag in Router Advertisements:

/ipv6/nd/prefix/default set autonomous=no

Configure DHCP servers

These will delegate the IPv4 address and the delegated IPv6 prefix to the downstream firewall.
Use dummy addresses for now, these will later be set dynamically.

/ip/pool/add name=wan ranges=203.0.113.25
/ip/dhcp-server/add name=wan interface=ether1 address-pool=wan lease-time=2m
/ip/dhcp-server/network/add comment="WAN" address=203.0.113.24/30 gateway=203.0.113.26
/ipv6/dhcp-server/add name=wan interface=ether1 prefix-pool=wan lease-time=2m
/ipv6/nd/set [ find default=yes ] interface=ether1 managed-address-configuration=yes other-configuration=yes mtu=1492

Configure the upstream PPPoE interface

My ISP requires VLAN 7. If yours doesn't, skip the VLAN creation and add the PPPoE client directly to an Ethernet interface.

/interface/vlan/add name=sfp1vlan7 interface=sfp1 vlan-id=7
/interface/pppoe-client/add name=pppoe interface=sfp1vlan7 add-default-route=yes use-peer-dns=yes disabled=no user=example password=****
/ipv6/dhcp-client/add interface=pppoe request=prefix pool-name=wan pool-prefix-length=56

We delegate the entire delegated prefix to the downstream firewall, so set pool-prefix-length to the size of the PD you get from your ISP.

Dynamically reconfigure the downstream interface's IP addresses and DHCPv4 server

This is where the magic happens. Add this script to the DHCPv6 client so it gets executed whenever we get a new lease from upstream (/ipv6/dhcp-client/set script="..."):

:if ($"pd-valid" = 1) do={
    :local wanAddr4 ([/interface/pppoe-client/monitor pppoe as-value once] -> "local-address");
    :local mask4 1;
    :local netAddr4;
    :local bcastAddr4;
    :local gatewayAddr4;

    :do {
        :set mask4 ($mask4 + 1);
        :set netAddr4 (($wanAddr4 >> $mask4) << $mask4);
        :set bcastAddr4 ($netAddr4 + (1 << $mask4) - 1);
    } while=(($wanAddr4 = $netAddr4) || ($wanAddr4 = $bcastAddr4));
    :set gatewayAddr4 ($netAddr4 + 1);
    :if ($gatewayAddr4 = $wanAddr4) do={:set gatewayAddr4 ($bcastAddr4 - 1)};

    /ip/address/remove [find interface=pppoe]
    /ip/address/set [find interface=ether1] address="$gatewayAddr4/$(32 - $mask4)"
    /ip/dhcp-server/network/set [find comment="WAN"] address="$netAddr4/$(32 - $mask4)" gateway=$gatewayAddr4
    /ip/pool/set wan ranges=$wanAddr4

    /ipv6/address/set [find interface=ether1 global=yes] address=([:pick $"pd-prefix" 0 [:find $"pd-prefix" "/"]] . "1/64")
}

For IPv4, the script calculates the smallest possible subnet which fits the PPPoE IPv4 address wanAddr4 as well as a second address. This second address gatewayAddr4 is the first address in the subnet, except when wanAddr4 is the first address. Then gatewayAddr4 is the last address in the subnet. It then removes the wanAddr4 from the PPPoE interface. After adding the gatewayAddr4 to the downstream interface, it reconfigures the DHCPv4 server to delegate the wanAddr4 to the downstream firewall, along with the matching gateway gatewayAddr4.

For IPv6, it sets the IPv6 address of the downstream interface by merging the delegated prefix with ::1. This means the first /64 subnet of the delegated prefix is used for the link to the downstream firewall. The firewall must not use this subnet for other interfaces, but it can use it to generate a WAN address. For OPNsense, that's Optional prefix ID 0x0 and an Optional interface ID of your choice (e. g. 0x2).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment