RouterOS networking and log collection

I configured MikroTik RouterOS with DoH, VLANs, DHCP, and firewall rules. CRS304 handles local Mac/Linux 10GbE traffic and routing. For infrastructure monitoring, counters stay on NAND when the Linux Syslog host is down.

Netwatch checks the destination and switches between Syslog delivery and local storage.

Background and Motivation

The requirements are:

  1. Security and Privacy: Encrypt DNS queries (DoH) and completely prohibit plaintext communication on UDP/TCP port 53.
  2. Network Isolation: Create VLANs tailored to different purposes (e.g., vlan10, vlan20, vlan30) and control routing to prevent mutual interference.
  3. Reliable Monitoring and Log Collection: Capture statistics of unauthorized access and packet drops, and send them to a Linux Syslog server. However, if the Syslog server is down, these logs should be pooled internally (on the router’s NAND) so they can be retrieved after recovery.

One five-minute job always runs. Netwatch only updates a state flag rather than enabling or disabling the scheduler.

Where This Fits in the Overall Topology

Port1 is WAN, Port2 Mac, Port3 Linux, Port4 Wi-Fi AP, and Port5 console. Wired 10GbE is local-first; other segments and policies handle external access. ISP inbound is normally dropped.

RouterOS holds the VLAN boundary and monitoring events, retaining basic counters during Linux downtime.


Main Configuration: Building the Network Infrastructure

I made ether1 the WAN interface and enabled DHCP.

  /interface ethernet set ether1 name=WAN
/ip dhcp-client add interface=WAN disabled=no
  

1. DNS (Enabling DoH)

DNS queries use DoH (DNS over HTTPS).

  /ip dns set allow-remote-requests=yes \
    servers=1.1.1.1,1.0.0.1 \
    use-doh-server=https://1.1.1.1/dns-query \
    verify-doh-cert=yes
  

The firewall prohibits outbound UDP/TCP 53. The router uses DoH; clients use their VLAN gateway IP for DNS.

2. VLAN and Bridge Setup

I assigned vlan10, vlan20, and vlan30 to ether4, ether2, and ether3 respectively.

  /interface vlan
add name=vlan10 interface=bridge vlan-id=10
add name=vlan20 interface=bridge vlan-id=20
add name=vlan30 interface=bridge vlan-id=30

/interface bridge vlan
add bridge=bridge tagged=bridge,ether4 vlan-ids=10
add bridge=bridge tagged=bridge,ether2 vlan-ids=20
add bridge=bridge tagged=bridge,ether3 vlan-ids=30

/interface bridge port
set [find interface=ether4] pvid=10
set [find interface=ether2] pvid=20
set [find interface=ether3] pvid=30

/ip address
add address=192.168.10.1/24 interface=vlan10
add address=192.168.20.1/24 interface=vlan20
add address=192.168.30.1/24 interface=vlan30
  

3. DHCP Server Configuration

This example distributes DHCP addresses only on vlan10.

  /ip pool add name=pool10 ranges=192.168.10.100-192.168.10.200
/ip dhcp-server
add name=dhcp10 interface=vlan10 address-pool=pool10 disabled=no
/ip dhcp-server network
add address=192.168.10.0/24 gateway=192.168.10.1 dns-server=192.168.10.1
  

4. Firewall (Filters)

Rules separate VLANs, block unauthorized external traffic, and allow management from vlan88. The forward chain drops vlan10–vlan20 traffic and allows new LAN-to-WAN connections.

  /ip firewall filter
# input
add chain=input connection-state=invalid action=drop comment="Drop invalid input"
add chain=input connection-state=established,related action=accept comment="Allow est/rel input"
add chain=input protocol=udp in-interface=ether1 src-port=67 dst-port=68 action=accept comment="Allow DHCP from ISP"
add chain=input protocol=tcp src-address=192.168.88.2 dst-port=22,8291,8728,8729 action=accept comment="Mgmt from vlan88"
add chain=input protocol=udp src-address=192.168.88.2 dst-port=161 action=accept comment="SNMP from vlan88"
add chain=input protocol=tcp src-address=192.168.88.2 dst-port=80,443 action=accept comment="Web mgmt from vlan88"
add chain=input protocol=icmp src-address=192.168.88.2 action=accept comment="ICMP from vlan88"
add chain=input action=drop log=yes log-prefix="IN_DROP:"

# forward
add chain=forward connection-state=invalid action=drop comment="Drop invalid forward"
add chain=forward action=fasttrack-connection connection-state=established,related hw-offload=yes comment="FastTrack"
add chain=forward connection-state=established,related action=accept comment="Allow est/rel forward"
add chain=forward in-interface=vlan10 out-interface=vlan20 action=drop comment="Isolate vlan10->vlan20"
add chain=forward in-interface=vlan20 out-interface=vlan10 action=drop comment="Isolate vlan20->vlan10"
add chain=forward connection-state=new out-interface=ether1 action=accept comment="LAN->WAN new"
add chain=forward action=drop comment="Drop remaining forward"

# output
add chain=output connection-state=established,related action=accept comment="Allow est/rel output"
add chain=output protocol=udp out-interface=ether1 src-port=68 dst-port=67 action=accept comment="Allow DHCP client"
add chain=output protocol=udp out-interface=ether1 dst-port=123 action=accept comment="Allow NTP"
add chain=output protocol=icmp out-interface=ether1 action=accept comment="Allow ping"
add chain=output protocol=tcp out-interface=ether1 dst-port=80,443 action=accept comment="Allow HTTP/HTTPS"
add chain=output protocol=udp dst-address=192.168.88.2 dst-port=514 action=accept comment="Syslog"
add chain=output protocol=tcp dst-address=192.168.88.2 dst-port=3100 action=accept comment="Loki ingest"
add chain=output action=drop comment="Drop remaining output"
  

Implementing State-Driven Syslog and Five-Minute Counter Snapshots

Normal operation sends UDP Syslog to Linux. During downtime, the router retains drop and attack counters.

Netwatch updates only linux_up. The five-minute counter_tick sends UDP and resets counters when UP, or appends to NAND counter.log when DOWN.

Base Logging and Counter DROP Setup

I added drop-counter rules with a rate limit to avoid excessive logging.

  /system logging action set memory memory-lines=8
/system logging disable [find action=disk]

/system logging action
add name=to-syslog target=remote remote=LINUX_IP remote-port=514 src-address=MGMT_IP bsd-syslog=yes

# Also send system,info to output COUNTER to syslog
/system logging
add topics=system,info action=to-syslog
add topics=firewall,info action=to-syslog
add topics=ssh,warning action=to-syslog
add topics=firewall,info action=memory

/ip firewall filter
add chain=input in-interface=WAN_IF protocol=tcp dst-port=22 connection-state=new \
    action=drop comment="CNT_SSH_IN" log=yes log-prefix="SSH_FAIL " limit=20/1m,40
add chain=input in-interface=WAN_IF connection-state=new action=drop \
    comment="CNT_DROP_IN" log=yes log-prefix="DROP_IN " limit=30/1m,60
add chain=forward in-interface=WAN_IF connection-state=new action=drop \
    comment="CNT_DROP_FW" log=yes log-prefix="DROP_FW " limit=30/1m,60
  

State Monitoring with Netwatch (90 seconds)

Ping LINUX_IP every 90 seconds and update linux_up.

  # Initial value: Start as UP
:global linux_up true

/system script add name=netwatch_up source={
  :global linux_up true
  :log info "NW:UP Linux reachable"
}
/system script add name=netwatch_down source={
  :global linux_up false
  :log warning "NW:DOWN Linux unreachable"
}

# 90s interval monitoring
/tool netwatch add host=LINUX_IP interval=00:01:30 up-script=netwatch_up down-script=netwatch_down
  

The 5-Minute Counter Processing Script

The five-minute scheduler checks linux_up.

  • When UP: It formats the counter values into JSON, outputs them to Syslog, and then resets the counters.
  • When DOWN: Instead of sending them to Syslog, it appends the JSON to a file named counter.log on the local NAND (without resetting the counters).
  /system script add name=counter_tick source={
  :global linux_up

  :local now [/system clock get time]
  :local date [/system clock get date]
  :local ssh [/ip firewall filter get [find comment="CNT_SSH_IN"] packets]
  :local in  [/ip firewall filter get [find comment="CNT_DROP_IN"] packets]
  :local fw  [/ip firewall filter get [find comment="CNT_DROP_FW"] packets]
  :local json ("{\"date\":\"$date\",\"time\":\"$now\",\"ssh_fail\":$ssh,\"drop_in\":$in,\"drop_fw\":$fw}")

  :if ($linux_up = true) do={
    # ---- UP: Send to syslog(UDP) -> reset immediately ----
    /log info ("COUNTER " . $json)
    /ip firewall reset-counters
  } else={
    # ---- DOWN: Append to one file on NAND (no reset) ----
    :if ([:len [/file find name=counter.log]] = 0) do={
      /file print file=counter.log where name=counter.log
      /file set counter.log contents=$json
    } else={
      /file set counter.log contents=([/file get counter.log contents] . "\n" . $json)
    }
  }
}

# Run constantly every 5m (behavior branches automatically on UP/DOWN)
/system scheduler add name=counter_tick_5m interval=5m on-event=counter_tick
  

counter_tick owns counter and file changes. Netwatch only changes true/false.

This sends five-minute snapshots rather than replaying every missed event over UDP. It does not reconstruct short spikes completely.

Data Retrieval Upon Recovery (Linux Side)

After recovery, Linux retrieves counter.log over SSH, removes the router copy, and resets counters.

  #!/usr/bin/env bash
ROUTER=192.168.30.1
DEST=/var/log/routeros/counter-recovered.log
mkdir -p "$(dirname "$DEST")"

if ssh admin-full@"$ROUTER" '[:len [/file find name=counter.log]]' >/dev/null 2>&1; then
  scp admin-full@"$ROUTER":counter.log "$DEST.tmp" || exit 0
  [ -f "$DEST" ] && cat "$DEST.tmp" >> "$DEST" || mv "$DEST.tmp" "$DEST"
  rm -f "$DEST.tmp"
  ssh admin-full@"$ROUTER" '/file remove counter.log; /ip firewall reset-counters'
fi
  

Results and Operation

VLAN separation and DoH were configured. UDP still lacks delivery guarantees, but Netwatch provides local storage when the Syslog destination is down, retaining statistics during maintenance.

The local-first wired setup and separate external/IoT segments use counter_tick_5m and counter.log for basic monitoring.

Future Considerations

UP/DOWN transitions may lose counters for seconds to tens of minutes because the 90-second Netwatch and five-minute scheduler differ. I accepted this for small-network statistics. Direct Loki ingestion and finer Prometheus/Grafana exporter metrics remain possible extensions.