Introduction

When I upgraded my Vodafone cable connection to Vodafone FibreX installation, it came with a Huawei HG659 Home Gateway supplied as part of the connection, including some IPv6 support and 802.11ac WiFi. While I was not particularly keen on using a "telco CPE" as my home edge device (amongst other things they have a general reputation for being poorly secured), it was fast than anything else I had at the time so I planned to use it until I had a specific reason to need something else to guide the next purchase.

That specific reason came along when I wanted a proper Network "DMZ" for my home network to host some development systems (I often work from home). The Huawei HG659 supports NAT pinholes routed to an internal system on the (single) LAN, but otherwise does not provide any real DMZ network isolation.

I purchased a Mikrotik RB750Gr3 -- nicknamed the Mikrotik "hEX" -- to be my replacement home edge router. They are around NZ$120 from the main local Mikrotik reseller, making them a fairly cost effective home router for someone needing extra flexibility. The Mikrotik RB750Gr3 is a dual-core 850Mhz MIPS CPU, with 256MB of RAM, 16MB of flash, and 5 Gigabit Ethernet (copper 1GBase-T) interfaces (via a single Ethernet switch chip). It is packaged in an indoor case, and while it is capable of PoE via ether1 typically in a home environment it would be powered by a (supplied) 24V DC plug pack.

(The other common "advanced home user" replacement seems to be a Ubiquiti device like the UniFi Security Gateway which can be configured for VLAN tagging, or the Ubiquiti EdgeRouter. I choose the Mikrotik because I am very familiar with them from previous jobs, and client sites, and for single devices much prefer direct configuration to forced-management via a "Security Controller" as is required by the Ubiquiti UniFi line. Something like the Ubiquiti EdgeRouter X 5-port, also a similar price in New Zealand, and apparently configurable from the command line, may well work just as well as the Mikrotik RB750Gr3 for this purpose -- I have not tried it myself.)

For my home Vodafone FibreX configuration I wanted:

  • A "WAN" connection to the Vodafone supplied TechniColor TC4400VDF DOCSIS 3.1 cable modem

  • Multiple LAN ports switched together

  • A "DMZ" interface with its own routing firewall rules, that was separated from both the WAN and LAN

  • Equal support for both IPv4 and IPv6 (Vodafone FibreX has provided IPv6 by default since shortly before my connection was converted to FibreX)

  • The ability to use IPv6 to expose devices in the DMZ, at least to known external locations, without needing to use IPv4 NAT or a VPN

I also wanted to continue to use the Vodafone-supplied Huawei HG659 home gateway for its 802.11ac WiFi, since it is the only 802.11ac AP in my house at present; I achieved that by simply disconnecting the WAN interface of the Huawei HG659 but leaving the LAN interface connected to the Mikrotik RB750Gr3 -- and changing the LAN address of the Huawei HG659 away from my LAN default gateway address. This means the Huawei HG659 acts as an 802.11ac WiFi to Gigabit Ethernet bridge (which also allows using the Huawei HG659 LAN ports as an additional Ethernet switch, that is handy as it is on my "comms" UPS). Presumably the Huawei HG659 is vulnerable to the Wifi KRACK replay attacks, but Vodafone issued a Huawei HG659 firmware update in August 2017, available for download via the Huawei HG659 user guide page, so maybe there will be another update available later in the year to fix more issues. In my configuration (without the WAN port connected), Vodafone will not be able to upgrade my Huawei HG659, but they do give instructions on manually installing the upgrade.

Configuration

The configuration I chose is based on a GeekZone Forum example of Mikrotik configuration for Vodafone FibreX, a Mikrotik Wiki guide to "Securing Your Router", and a Mikrotik IPv6 Home Example. As well as a lot of experience configuring Mikrotik devices as routers over the years.

To do the initial configuration I used a reimplementation of the Mikrotik mac-telnet feature, which works directly over the Ethernet connection without requiring IP address configured. Looking now, I see there is a later fork of the mac-telnet reimplementation with more features, as well as at least one other older independent implementation. (These mac-telnet reimplementations are much easier to use on Linux / OS X than trying to install WINE to get the Windows Mikrotik MAC-Telnet running -- although that does work too, and I have done it in the past.) Of note, there is also a Wireshark dissector for the MAC-Telnet protocol, and a reverse engineered packet description of the MAC-Telnet protocol -- it looks like it uses Ethernet broadcast frames with IPv4 UDP-like packets to/from port 20561.

Interface layout

The Mikrotik RB750Gr3 is a 5-GigE (1GBase-T) interface device, with all ports connected to an internal Ethernet switch chip. For my use case I wanted:

  • ether1 as the WAN interface, connected to the Vodafone supplied TechniColor TC4400VDF cable modem

  • ether2, ether3, and ether4 as LAN interfaces, with fast switching amongst them; and

  • ether5 as the DMZ interface

so that is the configuration used below. The Ethernet switch chip functionality ("master-port" is used for the LAN interfaces, which are then all ether2 as far as the rest of the configuration is concerned), and the other two (ether1 for WAN; ether5 for DMZ) are stand alone interfaces.

The Vodafone FibreX configuration -- unlike earlier Vodafone cable modem gateways, but like many UFB connections -- uses VLAN tagging, with VLAN 10, on the WAN connection. I assume Vodafone do this to have a fairly consistent configuration on their Huawei HG659 devices, which they use across multiple different connection types. Unlike, many UFB connections, which still use PPPoE, the Vodafone FibreX connection continues to use what is now called "IPoE" -- IP over Ethernet without additional layers like PPPoE (which itself is IP over PPP over Ethernet).

This means that there is an additional logical interface

  • VLAN 10 on ether1, which I have called "fibrex" in this configuration

to which all the IP level configuration is attached. Raw (untagged) ether1 is only used to reach the management IP of the TechniColor TC4400VDF cable modem (on 192.168.100.1) -- and then only to check that it is alive, as the default page provides basically no information, and is password protected with unspecified passwords. (Sadly there is no equivalent of the Motorola SB5100 modem light status page.)

Interface configuration

To implement this interface layout, label the Ethernet interfaces, and join ether3 and ether4 to ether2 via the Ethernet switch:

/int ethernet set ether1 comment="WAN"
/int ethernet set ether2 comment="LAN"
/int ethernet set ether3 master-port=ether2 comment="LAN (switched)"
/int ethernet set ether4 master-port=ether2 comment="LAN (switched)"
/int ethernet set ether5 comment="DMZ"

then add the fibrex VLAN interface:

/int vlan add name=fibrex interface=ether1 vlan-id=10 comment="VLAN 10 on ether1"

to hold the IP configuration facing the Vodafone FibreX connection.

Mikrotik base configuration

  • Upgrade to a recent Mikrotik RouterOS; that was 6.40.3 when I did my install, but 6.40.4 or later is recommended now as 6.40.4 includes the Wifi KRACK improvements, as well as several IPv6 related fixes.

  • Set the system name to something to help you identify it, and enable IPv6 functionality:

    /system identity set name=MY-rb750gr3
    /system package enable ipv6
    /system reboot
    

    (a reboot is required to get the IPv6 modules running).

  • Set an admin password, and (optionally) add your own admin-level account:

    /password
    /user add copy-from=admin name=ME comment="MY FULL NAME"
    

    then log in with the new account, and set its password:

    /password
    
  • Disable unnecessary services:

    /ip service print
    /ip service set telnet disabled=yes
    /ip service set ftp disabled=yes
    /ip service set www disabled=yes
    /ip service set api disabled=yes
    /ip service set winbox disabled=yes
    /ip service set api-ssl disabled=yes
    

    then check what is left enabled:

    /ip service print where disabled=no
    
  • Restrict ssh access to known internal networks and trusted IPs:

    /ip service set ssh address=A.B.C.D/24,E.F.G.H/32
    /ip ssh set strong-crypto=yes
    /ip ssh print
    
  • Disable Mikrotik WinBox server (since I do not use it; the client only runs on Windows / WINE):

    /tool mac-server mac-winbox set [find] disabled=yes
    /tool mac-server mac-winbox print
    
  • Permit mac-telnet only from internal interfaces, by overriding default and changing default access to disabled:

    /tool mac-server add interface=ether2 disabled=no
    /tool mac-server add interface=ether3 disabled=no
    /tool mac-server add interface=ether4 disabled=no
    /tool mac-server add interface=ether5 disabled=no
    /tool mac-server print
    /tool mac-server set 0 disabled=yes
    /tool mac-server print
    

    and disable the MAC-based "ping" functionality completely:

    /tool mac-server ping set enabled=no
    /tool mac-server ping print
    
  • Turn off Mikrotik Neighbor Discovery on external interfaces:

    /ip neighbor discovery print
    /ip neighbor discovery set ether1 discover=no
    /ip neighbor discovery set fibrex discover=no
    /ip neighbor discovery print
    
  • Disable other extraneous services:

    /ip dns set allow-remote-requests=no
    /ip proxy set enabled=no
    /ip socks set enabled=no
    /ip upnp set enabled=no
    /ip cloud set ddns-enabled=no update-time=no
    /tool bandwidth-server set enabled=no
    

    (several of those default to off, but it is good to be sure they are turned off when unneeded).

  • Disable IPv6 Neighbor Discovery by default (we will enable it specifically on internal interfaces later):

    /ipv6 nd set [find interface=all] disabled=yes
    /ipv6 nd print
    

    (and Vodafone FibreX requires DHCPv6 to obtain IPv6 addresses for the WAN interface, and an IPv6 pool for the internal interfaces).

IPv4 interface configuration

The Vodafone FibreX configuration expects you to use DHCPv4 to obtain the IP address, and by default hands out short-life leases (about 10 minutes) from a dynamic pool. It is possible to request a static IP address, but that is delivered as a static DHCPv4 lease, so DHCPv4 is still required. (I have requested a static IPv4 address because I often work from home, and needed access added for my IPv4 address on several client's firewall.)

  • Configure the WAN interface, which needs to do DHCPv4 on the fibrex (VLAN 10 on ether1) interface:

    /ip dhcp-client add interface=fibrex add-default-route=yes use-peer-dns=no use-peer-ntp=no disabled=no
    

The internal addressing needs to use IPv4 "Site Local" (RFC1918) addresses, due to the practical exhaustion of the IPv4 global address pool about 10 years ago :-( I would strongly recommend picking a less common RFC1918 address -- not 192.168.1.0/24, 192.168.88.0/24, or any other vendor default -- to avoid future confusion.

  • Configure the LAN interface with a chosen RFC1918 address:

    /ip addr add interface=ether2 address=A.B.C.D/24 comment="LAN"
    
  • Configure the DMZ interface with another RFC1918 address (since "home" connections come with only a single IPv4 address :-( ):

    /ip addr add interface=ether5 address=E.F.G.H/24 comment="DMZ"
    

At this point the Mikrotik should route IPv4 traffic properly between the LAN and the DMZ -- but connections out to the Internet will fail due to the aforementioned IPv4 address exhaustion meaning that NAT is required -- see below for NAT configuration in the firewall section.

IPv6 interface configuration

The Vodafone FibreX provision of IPv6 is a dynamic IPv6 /56 delivered via DHCPv6; there is no option for static DHCPv6 leases, and the leases appear to be tied to the router's MAC address (so changing routers will result in completely new addresses). However the DHCPv6 leases are quite long (about 2 weeks), and renewal does appear to work, so the DHCPv6 addresses should be fairly consistent.

For IPv6 we need to request both an IPv6 address for the router's WAN interface and a pool (/56) of IPv6 addresses to use for allocating internal IP addresses. This is because IPv6 address allocation is designed to provide connectivity-based IP addresses, to minimise the size of the routing table. (There is a range of IPv6 Unique Local Addresses which are roughly equivalent to IPv4 RFC1918 addresses as "Site Local" addresses -- but they are not intended for use with Global Internet Routing, nor is NAT expected to be used with IPv6; instead end devices are expected to have multiple IPv6 addresses.)

  • Request the IPv6 address and pool from the fibrex interface:

    /ipv6 dhcp-client add interface=fibrex pool-name=fibrex-pool add-default-route=yes use-peer-dns=no request=address,prefix
    

Once we have the fibrex-pool we can then assign internal interfaces out of that pool:

  • The LAN first:

    /ipv6 address add interface=ether2 from-pool=fibrex-pool advertise=yes
    /ipv6 firewall address-list add list=ipv6_lan \
          address=[/ipv6 address get [/ipv6 address find interface=ether2 from-pool=fibrex-pool] address]
    
  • And then the DMZ:

    /ipv6 address add interface=ether5 from-pool=fibrex-pool advertise=yes
    /ipv6 firewall address-list add list=ipv6_dmz \
          address=[/ipv6 address get [/ipv6 address find interface=ether5 from-pool=fibrex-pool] address]
    

Because these addresses are dynamic (drawn from a pool, which could change), we add them into "/ipv6 firewall address-list" entries to make it easier to use in firewall rules. (We could arrange for scripts to be run each time the pool changes, and thus these IPs change, but in practice in the last few weeks they have been very stable, so I have not yet automated updating the firewall address-lists on pool change.)

The "advertise=yes" makes the address eligible for the Mikrotik to advertise it for SLACC, which I have previously found worked best on my network (due to the Huawei HG659 DHCPv6 handing out duplicate addresses :-( ). This also avoids the need to set up stateful DHCPv6 on the internal interfaces.

To actually enable Neighbor Discovery Router Announcements (for SLAAC) on these interfaces, because we disabled it globally above, we need to configure Neighbor Discovery for these internal interfaces:

  • On the LAN:

    /ipv6 nd add interface=ether2 disabled=no ra-interval=3m20s-10m \
          ra-delay=3s mtu=unspecified reachable-time=unspecified \
          retransmit-interval=unspecified ra-lifetime=30m hop-limit=unspecified \
          advertise-mac-address=yes advertise-dns=no \
          managed-address-configuration=no other-configuration=no comment="LAN (SLAAC)"
    
  • On the DMZ:

    /ipv6 nd add interface=ether5 disabled=no ra-interval=3m20s-10m \
        ra-delay=3s mtu=unspecified reachable-time=unspecified \
        retransmit-interval=unspecified ra-lifetime=30m hop-limit=unspecified \
        advertise-mac-address=yes advertise-dns=no \
        managed-address-configuration=no other-configuration=no comment="DMZ (SLAAC)"
    

Internet edge firewalling

The firewall configuration becomes fairly complex because we have three routing interfaces (WAN = Internet; LAN; DMZ), as well as the Mikrotik itself, and two network protocols (IPv4 and IPv6) which have completely separate addresses and firewall rules. This means that we need firewall rules to cover:

  • LAN to Internet

  • DMZ to Internet

  • LAN to DMZ

  • DMZ to LAN

  • Internet to LAN

  • Internet to DMZ

for both IPv4 and IPv6. Some of those can be very generic policies (eg, "Internet to LAN" should not allow any "unexpected" traffic; "LAN to Internet" may be okay allowing pretty much everything out), but others need a fair amount of detail.

In addition the IP addresses of the WAN interface is notionally dynamic for both IPv4 and IPv6, and the LAN and DMZ interface ranges are also dynamic for IPv6 (due to being auto-assigned out of DHCPv6 provided pools). And IPv4 Internet access requires NAT, due to home connections being provided with only a single IPv4 IP address to share amongst several internal devices.

Since IPv4 and IPv6 are essentially completely independent, they are covered separately below.

IPv4 Firewalling

IPv4 Address Lists

The easiest way to obtain flexibility in Mikrotik firewall rule sets is to make extensive use of the "/ip firewall address-list" facility to attach names to groups of IPv4 addresses -- and then use only those names (rather than literal IPv4 addresses) in the rule set as much as possible.

We start with definitions of the internal interfaces:

/ip firewall address-list add list=ipv4_lan address=A.B.C.D/24
/ip firewall address-list add list=ipv4_dmz address=E.F.G.H/24

which should match the IPv4 subnets used for the interface definitions above (a similar auto-define approach could be used to set the IPv4 address-lists as was used with the IPv6 address lists, but since the IPv4 internal addresses are fixed it does not seem necessary).

Another useful address list is a list of addresses which can externally manage the Mikrotik (eg, a work address for when you need to get into your home connection):

/ip firewall address-list add list=ipv4_ext_mgmt address=G.H.I.J comment="EXPLANATION"

repeat as needed to add multiple addresses; using, eg, a DNS name or site/company name in the comments help with figuring out which one is which later on when they inevitably need to be updated.

It is also useful to define a "bogon" address list, which should not appear on the Internet -- this IPv4 list taken from RFC6890:

/ip firewall address-list
add address=0.0.0.0/8 comment=RFC6890 list=ipv4_bogon
add address=10.0.0.0/8 comment=RFC6890 list=ipv4_bogon
add address=172.16.0.0/12 comment=RFC6890 list=ipv4_bogon
add address=192.168.0.0/16 comment=RFC6890 list=ipv4_bogon
add address=169.254.0.0/16 comment=RFC6890 list=ipv4_bogon
add address=127.0.0.0/8 comment=RFC6890 list=ipv4_bogon
add address=224.0.0.0/4 comment=Multicast list=ipv4_bogon
add address=198.18.0.0/15 comment=RFC6890 list=ipv4_bogon
add address=192.0.0.0/24 comment=RFC6890 list=ipv4_bogon
add address=192.0.2.0/24 comment=RFC6890 list=ipv4_bogon
add address=198.51.100.0/24 comment=RFC6890 list=ipv4_bogon
add address=203.0.113.0/24 comment=RFC6890 list=ipv4_bogon
add address=100.64.0.0/10 comment=RFC6890 list=ipv4_bogon
add address=240.0.0.0/4 comment=RFC6890 list=ipv4_bogon
add address=192.88.99.0/24 comment="6to4 relay Anycast [RFC 3068]" list=ipv4_bogon
/

(Note the trailing "/" to return the Mikrotik context back to the top level.)

IPv4 NAT to the Internet

Once we have done that, we can define the NAT rules needed for Internet access:

/ip firewall nat add chain=srcnat action=masquerade out-interface=fibrex src-address-list=ipv4_lan
/ip firewall nat add chain=srcnat action=masquerade out-interface=fibrex src-address-list=ipv4_dmz

which specifies that any IPv4 traffic from the LAN or DMZ address ranges, allowed out by the firewall rules to the Internet, will be sent out using the current IP of the fibrex interface. The use of "action=masquerade" means it should automatically adapt if the IPv4 external address ever changes.

IPv4 ICMP filtering

Over the years IPv4 ICMP has acquired a number of special case uses which probably should not be used on the modern Internet, so it can be useful to be more specific about ICMP required. We can do this by creating an ipv4_icmp filter that whitelists the expected types and blocks all other ICMP.

This list should not be considered exhaustive, but is probably the minimum required for a functioning IPv4 connection:

/ip firewall filter
add chain=ipv4_icmp protocol=icmp icmp-options=0:0 action=accept comment="echo reply"
add chain=ipv4_icmp protocol=icmp icmp-options=3:0 action=accept comment="net unreachable"
add chain=ipv4_icmp protocol=icmp icmp-options=3:1 action=accept comment="host unreachable"
add chain=ipv4_icmp protocol=icmp icmp-options=3:4 action=accept comment="host unreachable fragmentation required"
add chain=ipv4_icmp protocol=icmp icmp-options=4:0 action=accept comment="allow source quench"
add chain=ipv4_icmp protocol=icmp icmp-options=8:0 action=accept comment="allow echo request"
add chain=ipv4_icmp protocol=icmp icmp-options=11:0 action=accept comment="allow time exceed"
add chain=ipv4_icmp protocol=icmp icmp-options=12:0 action=accept comment="allow parameter bad"
add chain=ipv4_icmp protocol=icmp action=drop comment="deny all other types"
/

(Again note the trailing "/" to reset the Mikrotik context to the top level.)

IPv4 Input/Output from the Mikrotik

The Mikrotik firewalling has an "input" chain for traffic to the Mikrotik itself, an "output" chain for traffic from the Mikrotik itself, and a "forward" chain for traffic originating outside the Mikrotik destined for somewhere outside the Mikrotik.

Having defined all the above address lists and helper filters, we can now define the "input" and "output" chains. These need to allow DHCPv4 (RC2131), as well as management traffic from known locations -- and block unexpected traffic from external locations.

The IPv4 input filter:

/ip firewall filter
add chain=input action=accept connection-state=established,related
add chain=input action=jump   protocol=icmp jump-target=ipv4_icmp
add chain=input action=accept protocol=udp in-interface=fibrex src-port=67 dst-port=68 comment="IPv4 DHCP"
add chain=input action=accept in-interface=fibrex src-address-list=ipv4_ext_mgmt
add chain=input action=accept in-interface=ether2 src-address-list=ipv4_lan
add chain=input action=accept in-interface=ether5 src-address-list=ipv4_dmz
add chain=input action=drop   in-interface=fibrex
add chain=input action=drop   in-interface=ether1
add chain=input action=reject
/

and the IPv4 output filter:

/ip firewall filter
add chain=output action=accept connection-state=established,related
add chain=output action=jump   protocol=icmp jump-target=ipv4_icmp
add chain=output action=accept protocol=udp out-interface=fibrex src-port=68 dst-port=67
add chain=output action=accept protocol=udp port=123 dst-address-list=ipv4_ntp_servers comment="NTP"
add chain=output action=reject out-interface=fibrex
add chain=output action=reject out-interface=ether1
add chain=output comment="Mikrotik Beacons to LAN" \
    dst-address=255.255.255.255 out-interface=ether2 port=5678 protocol=udp
add chain=output action=reject out-interface=ether2 log=yes log-prefix="To LAN"
add chain=output comment="Mikrotik Beacons to DMZ" \
    dst-address=255.255.255.255 out-interface=ether5 port=5678 protocol=udp
add chain=output action=reject out-interface=ether5 log=yes log-prefix="To DMZ"
add chain=output action=drop
/

Where 255.255.255.255 is the IPv4 broadcast address.

Of note, IPv4 ICMP is filtered via the "known good" ICMPv4 whitelist defined above, in both directions, and DHCPv4 is allowed on the fibrex VLAN tagged interface, in both directions, and traffic from management sources to the Mikrotik is permitted (including, by choice, all the internal addresses -- that could be locked down further if desired).

Traffic from the external interfaces is simply dropped without logging (because the Internet is filled with constant scanning), but traffic to the Internet is logged to help debug missing rules. Traffic to internal interfaces also logged to help determine (a) if there are missing rules and (b) if anything is trying to reach into the internal network.

IPv4 traffic through the Mikrotik

We can fairly easily define default policies for traffic between the three interfaces:

  • Anything from the Internet to an internal interface should be blocked unless it is part of a connection established outbound, or a specific rule allowing traffic to the DMZ

  • Anything to the Internet should be allowed by default from known IPs

  • LAN to DMZ traffic should be allowed by default, but may be more filtered later

  • DMZ to LAN traffic should be limited, initially just ICMPv4 (but maybe later, eg, DNS and logging)

This means that only two of these cases need special treatment, LAN to DMZ, and DMZ to LAN:

/ip firewall filter
add chain=ipv4_lan_to_dmz action=accept
/

/ip firewall filter
add chain=ipv4_dmz_to_lan action=jump jump-target=ipv4_icmp
add chain=ipv4_dmz_to_lan action=reject
/

And then we can define some policies for traffic arriving on the LAN:

/ip firewall filter
add chain=ipv4_lan_out action=jump   src-address-list=ipv4_lan dst-address-list=ipv4_dmz in-interface=ether2 out-interface=ether5 jump-target=ipv4_lan_to_dmz
add chain=ipv4_lan_out action=accept src-address-list=ipv4_lan dst-address-list=!ipv4_bogons in-interface=ether2 out-interface=fibrex
add chain=ipv4_lan_out action=reject
/

and for traffic arriving on the DMZ:

/ip firewall filter
add chain=ipv4_dmz_out action=jump   src-address-list=ipv4_dmz dst-address-list=ipv4_lan in-interface=ether5 out-interface=ether2 jump-target=ipv4_dmz_to_lan
add chain=ipv4_dmz_out action=accept src-address-list=ipv4_dmz dst-address-list=!ipv4_bogons in-interface=ether5 out-interface=fibrex
add chain=ipv4_dmz_out action=reject
/

which use those LAN/DMZ policies for traffic between the LAN and DMZ interfaces, and the bogon list to filter traffic out to the Internet. "Everything else" unexpected is rejected; but in practice there should not be anything else.

Once those are defined, we can define a general IPv4 forwarding policy which hooks all of these together, and adds blocks for unexpected inbound traffic:

/ip firewall filter
add chain=forward action=fasttrack-connection connection-state=established,related comment="FastTrack (if possible)"
add chain=forward action=accept               connection-state=established,related comment="Other Established, Related"
add chain=forward action=drop                 connection-state=invalid comment="Drop invalid" log=yes log-prefix=Invalid
add chain=forward action=jump in-interface=ether2 jump-target=ipv4_lan_out
add chain=forward action=jump in-interface=ether5 jump-target=ipv4_dmz_out
add chain=forward action=drop in-interface=fibrex connection-nat-state=!dstnat connection-state=new comment="Inbound non-NAT" log=yes log-prefix=!NAT
add chain=forward action=drop in-interface=fibrex
add chain=forward action=drop in-interface=ether1
add chain=forward action=reject
/

and then our IPv4 firewall policy is complete, if fairly minimal.

The main thing I anticipate adding over time is some DMZ to LAN pinholes, maybe some Internet to DMZ pinholes (using IPv4 Destination NAT) and perhaps some further lock down of the LAN to DMZ traffic. Since those are all in their own rule sets they should be fairly easy to modify.

IPv6 Firewalling

IPv6 Firewalling is completely separate from IPv4 Firewalling on the Mikrotik (and many devices), due to using completely separate IP addresses, but by using "firewall address-lists" the shape of the firewall rules can (and arguably should) look very similar.

IPv6 address lists

We can define external management addresses:

/ipv6 firewall address-list add list=ipv6_ext_mgmt address=AAAA:BBBB:CCC:DDDD:EEEE:FFFF:GGGG:HHHH comment="DESCRIPTION"

to go along with the ipv6_lan and ipv6_dmz definitions that we calculated above (when defining the IPv6 IP addresses).

We can also add some helper address lists for known IPv6 types:

/ipv6 firewall address-list add list=ipv6_link_local address=fe80::/16
/ipv6 firewall address-list add list=ipv6_multicast  address=ff02::/16

and addresses which should not appear on the Internet:

/ipv6 firewall address-list add list=ipv6_bogons address=fc00::/7 comment="IPv6 Unique Local Addresses"

(in this case IPv6 ULA addresses mentioned above, which are site local).

IPv6 ICMP filtering

ICMPv6 is even more critical to IPv6 than ICMPv4 is to IPv4, so we need to be careful with filtering; there are also fewer "tried to 20 years ago, do not use now" ICMPv6 entries. However to match the pattern of firewall rules between IPv4 and IPv6, I also defined an ICMPv6 whitelist. This should definitely be considered the minimum and will almost certainly need expanding over time; hence the "accept but log" at the end before the "default drop" -- thus accepting everything, but tracking "unexpected" traffic.

/ipv6 firewall filter
add chain=ipv6_icmp action=accept protocol=icmpv6 icmp-options=1:0-255 comment="Destination Unreachable"
add chain=ipv6_icmp action=accept protocol=icmpv6 icmp-options=2:0-255 comment="Packet Too Big"
add chain=ipv6_icmp action=accept protocol=icmpv6 icmp-options=3:0-255 comment="Time Exceeded"
add chain=ipv6_icmp action=accept protocol=icmpv6 icmp-options=4:0-255 comment="Parameter Problem"
add chain=ipv6_icmp action=accept protocol=icmpv6 icmp-options=128:0-255 comment="Echo Request"
add chain=ipv6_icmp action=accept protocol=icmpv6 icmp-options=129:0-255 comment="Echo Reply"
add chain=ipv6_icmp action=accept protocol=icmpv6 icmp-options=132:0-255 comment="Multicast Listener Done"
add chain=ipv6_icmp action=accept protocol=icmpv6 icmp-options=133:0-255 comment="Router Solicitation (NDP)"
add chain=ipv6_icmp action=accept protocol=icmpv6 icmp-options=134:0-255 comment="Router Announcement (NDP)"
add chain=ipv6_icmp action=accept protocol=icmpv6 icmp-options=135:0-255 comment="Neighbor Solicitation (NDP)"
add chain=ipv6_icmp action=accept protocol=icmpv6 icmp-options=136:0-255 comment="Neighbor Announcement (NDP)"
add chain=ipv6_icmp action=accept protocol=icmpv6 icmp-options=137:0-255 comment="Neighbor Redirect (NDP)"
add chain=ipv6_icmp action=accept protocol=icmpv6 icmp-options=143:0-255 comment="Version 2 Multicast Listener Report"
add chain=ipv6_icmp action=accept protocol=icmpv6 log=yes log-prefix=ICMPv6
add chain=ipv6_icmp action=drop
/

We can also add another filter for "just ping" to be used in more specific scenarios:

/ipv6 firewall filter
add chain=ipv6_ping action=accept protocol=icmpv6 icmp-options=128:0-255 comment="Echo Request"
add chain=ipv6_ping action=accept protocol=icmpv6 icmp-options=129:0-255 comment="Echo Reply"
/

IPv6 Input/Output from the Mikrotik

Having done that, we can define somewhat longer lists of traffic to/from the Mikrotik itself. Note that due to the extensive use of IPv6 Link Local addresses for key functions it is important that we allow those on each interface (the same addresses are used on each interface, with an interface specific route). We also need to allow IPv6 Link Local Multicast for the same reason. Like IPv4 we obviously need to allow DHCP, since that is how we get the addresses, but DHCPv6 is a different protocol on different UDP ports from DHCPv4 on IPv4.

Then we have an IPv6 input rule set looking similar to the IPv4 one:

/ipv6 firewall filter
add chain=input action=accept connection-state=established,related
add chain=input action=jump   protocol=icmpv6 jump-target=ipv6_icmp
add action=accept chain=input comment="DHCPv6 Replies" \
    dst-address-list=ipv6_link_local dst-port=546 in-interface=fibrex \
    protocol=udp src-address-list=ipv6_link_local src-port=547
add chain=input action=accept in-interface=fibrex src-address-list=ipv6_ext_mgmt
add chain=input action=accept in-interface=ether2 src-address-list=ipv6_lan
add chain=input action=accept in-interface=ether2 src-address-list=ipv6_link_local
add chain=input action=accept in-interface=ether2 src-address-list=ipv6_multicast
add chain=input action=accept in-interface=ether5 src-address-list=ipv6_dmz
add chain=input action=accept in-interface=ether5 src-address-list=ipv6_link_local
add chain=input action=accept in-interface=ether5 src-address-list=ipv6_multicast
add chain=input action=drop   in-interface=fibrex
add chain=input action=drop   in-interface=ether1
add chain=input action=reject
/

and a similar looking output rule set:

/ipv6 firewall filter
add chain=output action=accept connection-state=established,related
add chain=output action=jump   protocol=icmpv6 jump-target=ipv6_icmp
add action=accept chain=output comment=DHCPv6 dst-address=ff02::1:2/128 \
    dst-port=547 out-interface=fibrex protocol=udp \
    src-address-list=ipv6_link_local src-port=546
add chain=output action=accept out-interface=fibrex protocol=udp dst-port=68-69 comment="DHCP"
add chain=output action=reject out-interface=fibrex
add chain=output action=reject out-interface=ether1
add chain=output comment="Mikrotik Beacons to LAN" dst-address=ff02::1/128 \
    out-interface=ether2 port=5678 protocol=udp
add chain=output action=reject out-interface=ether2 log=yes log-prefix="To LAN"
add chain=output comment="Mikrotik Beacons to DMZ" dst-address=ff02::1/128 \
    out-interface=ether5 port=5678 protocol=udp
add chain=output action=reject out-interface=ether5 log=yes log-prefix="To DMZ"
add chain=output action=drop
/

The ff02::1/128 address is the "all nodes in link-local" address is basically the IPv6 equivalent of an IPv4 broadcast on a LAN segment; IPv6 does not have broadcast addresses as such.

IPv6 traffic through the Mikrotik

The IPv6 firewall for traffic through the Mikrotik is like the IPv4 firewall for traffic through the Mikrotik, but simpler because it does not require any NAT -- we have globally unique addresses everywhere (I have chosen not to use IPv6 Unique Local Addresses at this time).

The main difference is that we can also receive traffic from the Internet direct to those global addresses -- and for now I have chosen to allow ICMPv6 Ping and nothing else, to help with debugging routing and other issues.

So we have IPv6 firewall chains for traffic from LAN to DMZ and DMZ to LAN:

/ipv6 firewall filter
add chain=ipv6_lan_to_dmz action=accept
/

/ipv6 firewall filter
add chain=ipv6_dmz_to_lan action=jump protocol=icmpv6 jump-target=ipv6_ping
add chain=ipv6_dmz_to_lan action=reject
/

which are then used by IPv6 firewall chains for traffic originating on the LAN and DMZ:

/ipv6 firewall filter
add chain=ipv6_lan_out action=jump   src-address-list=ipv6_lan \
    dst-address-list=ipv6_dmz in-interface=ether2 out-interface=ether5 \
    jump-target=ipv6_lan_to_dmz
add chain=ipv6_lan_out action=accept src-address-list=ipv6_lan \
    dst-address-list=!ipv6_bogons in-interface=ether2 out-interface=fibrex
add chain=ipv6_lan_out action=reject
/

/ipv6 firewall filter
add chain=ipv6_dmz_out action=jump   src-address-list=ipv6_dmz \
    dst-address-list=ipv6_lan in-interface=ether5 out-interface=ether2 \
    jump-target=ipv6_dmz_to_lan
add chain=ipv6_dmz_out action=accept src-address-list=ipv6_dmz \
    dst-address-list=!ipv6_bogons in-interface=ether5 out-interface=fibrex
add chain=ipv6_dmz_out action=reject
/

to both handle LAN to DMZ and LAN to Internet -- and DMZ to LAN and DMZ to Internet -- traffic.

Then we have some IPv6 inbound firewall rules to handle Internet originated traffic:

/ipv6 firewall filter
add chain=ipv6_lan_in action=jump src-address-list=!ipv6_bogons \
    dst-address-list=ipv6_lan in-interface=fibrex out-interface=ether2 \
    protocol=icmpv6 jump-target=ipv6_ping
add chain=ipv6_lan_in action=reject
/

/ipv6 firewall filter
add chain=ipv6_dmz_in action=jump src-address-list=!ipv6_bogons \
    dst-address-list=ipv6_dmz in-interface=fibrex out-interface=ether5 \
    protocol=icmpv6 jump-target=ipv6_ping
add chain=ipv6_dmz_in action=reject
/

(If I were starting again I might have called these ipv6_to_lan and ipv6_to_dmz, and the "out" ones ipv6_from_lan and ipv6_from_dmz; but I wanted to be consistent with the already defined above IPv4 firewall chain names, and the Mikrotik makes it non-trivial to change firewall chain names.)

Once all of the above is defined, we can define a general IPv6 "forward" policy that hooks into all these other chains as required:

/ipv6 firewall filter
add chain=forward action=accept connection-state=established,related comment="Other Established, Related"
add chain=forward action=drop   connection-state=invalid comment="Drop invalid" log=yes log-prefix=Invalid
add chain=forward action=jump in-interface=ether2 jump-target=ipv6_lan_out
add chain=forward action=jump in-interface=ether5 jump-target=ipv6_dmz_out
add chain=forward action=jump in-interface=fibrex out-interface=ether2 jump-target=ipv6_lan_in
add chain=forward action=jump in-interface=fibrex out-interface=ether5 jump-target=ipv6_dmz_in
add chain=forward action=drop in-interface=fibrex
add chain=forward action=drop in-interface=ether1
add chain=forward action=reject
/

Then the basic IPv6 firewall should be complete, ready to be extended over time. If the IPv6 addresses change then the address-lists will need some tweaking, but in theory the rules themselves should be fairly static.

Other firewall related configuration

The IPv4 firewall state timeouts are relatively short by default in some cases so it can help to extend these:

/ip firewall connection tracking set tcp-fin-wait-timeout=5m \
    tcp-close-wait-timeout=5m tcp-last-ack-timeout=5m \
    tcp-time-wait-timeout=5m tcp-close-timeout=5m

and ideally we would do the same for IPv6, but there is no specific IPv6 firewall connection tracking options; it is unclear if the IPv4 settings also apply to IPv6, or if the IPv6 connection tracking times are simply not exposed.

Over time other firewall rules can be added to allow, eg,

  • NTP for time synchronisation via the WAN interface (to known NTP servers):

    /system ntp client set primary-ntp=A.B.C.D server-dns-names=foo.example.com enabled=yes
    /ip firewall address-list add list=ipv4_ntp_servers address=A.B.C.D
    

    which hooks into the IPv4 output firewall rule set above.

  • Allow the DMZ to use the LAN DNS server:

    /ip firewall address-list add list=ipv4_lan_dns_server address=E.F.G.H
    /ip firewall filter print where chain=ipv4_dmz_to_lan
    /ip firewall filter add chain=ipv4_dmz_to_lan protocol=udp dst-port=53 \
        dst-address-list=ipv4_lan_dns_server comment="DNS (UDP)" place-before=NN
    /ip firewall filter add chain=ipv4_dmz_to_lan protocol=tcp dst-port=53 \
        dst-address-list=ipv4_lan_dns_server comment="DNS (TCP)" place-before=NN
    

    which will need appropriate place-before=NN entries to put it into the right location in the rules.

Conclusion

With all of this set up, it should be possible to plug ether1 of the Mikrotik into the Vodafone TechniColor TC4400VDF in place of the Huawei HG659. Then power cycle the Vodafone TechniColor TC4400VDF to force it to forget the internal MAC addresses, and let the network know to expect a new connection. Once the TechniColor TC4400VDF boots, the Mikrotik should be able to get IPv4 and IPv6 addresses via DHCPv4 and DHCPv6. You can inspect the DHCP state with:

/ip dhcp-client print
/ip dhcp-client print detail

/ipv6 dhcp-client print
/ipv6 dhcp-client print detail

and in both cases you are looking for a status of "bound", and some appropriate IP addresses, and an appropriate lease expiry time (minutes for the IPv4 address; weeks for the IPv6 addresses).

For IPv6 you can also inspect the IPv6 pool allocated, and what was assigned to the LAN and DMZ interfaces:

/ipv6 pool print
/ipv6 address print detail where global and interface=ether2
/ipv6 address print detail where global and interface=ether5

Providing the addresses used from the IPv6 pool are at the very start of the pool (ie, first allocations) they should be fairy stable over reboot of the Mikrotik (on each boot it will revert to the start of the pool). The addresses on those interfaces should be compared with the address lists for the LAN / DMZ IPv6 addresses if there are issues with IPv6 reachability:

/ipv6 firewall address-list print where list=ipv6_lan
/ipv6 firewall address-list print where list=ipv6_dmz

and if they are out of sync, use the commands shown in the IPv6 address definition sections to update the address-lists with the current values.

This configuration has worked fairly reliably for me for the last month. The main issues have been:

  • Issues with DHCPv4 and DHCPv6, which eventually lead to fairly broadly allowing DHCPv4 and DHCPv6 on the WAN interface (I think what was happening was that the state relating to the DHCP request was timing out and the reply was being ignored; DHCP uses different addresses at different stages depending on whether or not it already has an IP address); and

  • Multiple issues with the Vodafone FibreX headend going away (the Vodafone TechniColor TC4400VDF showing no uplink/downlink), particularly around three weeks ago (where it went away twice within 30 minutes on each of 2 days; I think Vodafone were doing some sort of maintenance, but it is unclear exactly what -- and it happened in the middle of the work day rather than overnight).

I have also changed the LAN address of the Vodafone supplied Huawei HG659 to a different IPv4 IP and disabled unneeded functionality:

  • IPv6 RA (Route Announcements)

  • IPv6 DHCPv6

  • UPnP

so that it does not interfere, and then continued to use it as just an access point. It does complain it is "not connected to the Internet" (as the WAN interface is not connected, so its DHCP requests are failing) but otherwise it seems to work fine as just an access point.

Handling IPv6 DHCPv6 client/pool address changes

ETA 2017-11-12: After a few more weeks, it has turned out that the Vodafone "Dynamic but Stable" IPv6 addresses do end up changing often enough to be annoying (the change breaks the IPv6 firewalling, which breaks IPv6 for LAN clients, which causes delays in connecting :-( ). It also appears that the previous 2-week leases might have been reduced to a somewhat more sensible "several hours" lease time.

To handle this I have put some effort into Mikrotik scripting to track the changing LAN/DMZ IPv6 address ranges. Ideally this would happen when the IPv6 addresses themselves changed, but I cannot find a scripting hook on "/ipv6 address" or "/ipv6 pool" to use. The next best thing is to hook into the "/ipv6 dhcp-client" scipting features, and run a script when the DHCPv6 addresses are acquired, applied or removed. But since the IPv6 pool updates and IPv6 address updates from those pools might happen asynchronously, we need a bit of a delay before trying to update the "/ipv6 firewall address-list" entries -- I've chosen around 30 seconds as likely to be sufficent. Sadly there is not an easy way to schedule a script to "run in 30 seconds" (cf at on Unix systems); the best option seems to be to enable/disable a scheduler event that runs every 30 seconds, as a "one shot" run.

So the process is:

  • "/ipv6 dhcp-client ... script=..." which runs a "/system script" that enables the "update filters every 30 seconds" scheduler event.

  • In around 30 seconds, that script launches and (a) runs the script that will update the IPv6 address-lists, and (b) disables the "update filters every 30 seconds" scheduler event.

  • For some more robustness there is also another hourly scheduler event (which stays enabled) which also runs the same script to update the IPv6 address lists); hourly seemed often enough to minimise the "wrong IP" pain, while still keeping resource usage fairly low (amongst other things we are rewriting the config each time!)

The individual per-interface address list updates are simply the commands given earlier to set the "/ipv6 firewall address-list ..." entries, preceeded by a command to clear the existing address-list entries (to avoid them accumulating months of old history!).

The basic per-interface scripts are:

/system script
add name=ipv6-update-lan-range owner=ewen policy=read,write \
    source="/ipv6 firewall address-list remove [/ipv6 firewall \
            address-list find list=ipv6_lan]; \
    /ipv6 firewall address-list add list=ipv6_lan address=[/ipv6 \
          address get [/ipv6 address find interface=ether2 \
          from-pool=fibrex-pool] address]"
/

/system script
add name=ipv6-update-dmz-range owner=ewen policy=read,write \
    source="/ipv6 firewall address-list remove [/ipv6 firewall \
           address-list find list=ipv6_dmz]; \
    /ipv6 firewall address-list add list=ipv6_dmz address=[/ipv6 \
          address get [/ipv6 address find interface=ether5 \
          from-pool=fibrex-pool] address]"
/

(Note the use of ";" in between the two comamnds to separate them; the alternative is to embed CR (\r) and NL (\n) characters into the script.)

Having done that for convenience we combine them into one script which calls both:

/system script add name=ipv6-update-filters owner=ewen policy=read,write \
    source="/system script run ipv6-update-lan-range; \
            /system script run ipv6-update-dmz-range"

Then we can schedule that top level script hourly:

/system scheduler add name="ipv6-update-filters" \
        on-event="ipv6-update-filters" interval=1h

as a background precaution.

To make it run "on demand" for IPv6 DHCPv6 client changes, we need to create a delayed one-shot variant. To do this we make a place holder "once" script:

/system script add name="ipv6-update-filters-once" policy=read,write \
        source="/system script run ipv6-update-filters"

that initially just runs the top level script. Then we scheduled that to run every 30 seconds, but leave it disabled:

/system scheduler add name="ipv6-update-filters-once" \
        on-event="ipv6-update-filters-once" disabled=yes interval=30s

and update the script that is run to disable the scheduler event that started it, so that enabling the schduler event will result in a "once" run:

/system script set [/system script find name=ipv6-update-filters-once] \
        source="/system script run ipv6-update-filters; \
                /system scheduler set disabled=yes [/system scheduler \
                        find name=ipv6-update-filters-once]" \
        comment="Oneshot schedulable ipv6-update-filters"

and finally we create a script to enable that event when required:

/system script add name="ipv6-update-filters-in-30-seconds" \
        policy="read,write" source="/system scheduler set disabled=no \
                [/system scheduler find name=ipv6-update-filters-once]" \
        comment="Enable 'once' IPv6 Filter Update"

which we can test by hand with:

/system script run ipv6-update-filters-in-30-seconds

and then watch it with:

/system scheduler print
[...]
/system scheduler print
/ipv6 firewall address-list print

and we should see the "once" scheduler event get enabled, then after a while show that it has run once (run-count is 1), and be disabled again. Looking at the "/ipv6 firewall address-list print" should then show the updated addresses.

Once we are sure that it works, we can then hook it up to the IPv6 DHCPv6 client with:

/ipv6 dhcp-client set [/ipv6 dhcp-client find interface=fibrex] \
      script="/system script run ipv6-update-filters-in-30-seconds"

which in theory will run the "one-shot" filter update about 30 seconds after the DHCP change (and since it is idempotent, and via a scheduler event with its own event timing, it should not get run repeatedly very often -- and if it does, it should still work out okay). The hourly event remains as a backup.

Ideally there would be an easier way to express this "address-list contains the network of this interface" policy than the kludge described above, particularly with IPv6 address-lists where the underlying addresses are likely to change regularly for many users (IPv4 mostly avoids this problem by NAT and masquerade just tracking the changing IP). But hopefully IPv6 address changes will now require less manual intervention.

Of note, both freedommafia.net Mikrotik examples, and bluecrow.net Mikrotik examples are useful hints as to the range of things that can be done with Mikrotik Scripting. It takes a bit of creativity to express what you want, but the scripting language is reasonably full featured.