はじめに

今回は、MikroTikルーター上で基本的なネットワーク基盤(DNS DoH、VLAN分離、DHCP、ファイアウォール)を構築する手順と、Netwatch機能を活用してSyslogサーバーの死活状態に応じたログの送信・退避を自動的に切り替える仕組みについて解説する。特に今回は、CRS304 を「ローカル 10GbE スイッチ兼ルーター」として使う前提で、Mac と Linux の有線セグメントをローカル専用にしつつ、監視カウンタだけは確実に残すための運用に焦点を当てた。

小規模から中規模のネットワーク環境では、単一のルーターでルーティングからセキュリティ、ログ監視までを統合管理することが多い。そこで私は、セキュアなVLAN分離環境の構築と合わせて、「Syslogサーバーが落ちた際にもログを取りこぼさない仕組み」を実装することにした。

背景と目的

MikroTik RouterOSは非常に柔軟で強力なルーティング・ファイアウォール機能を持っている。ネットワークを構築するにあたり、以下の要件を満たす必要があった。

  1. セキュリティとプライバシー: DNSへの問い合わせを暗号化(DoH)し、平文のUDP/TCP 53番ポートでの通信を禁止する。
  2. ネットワークの分離: 用途に応じたVLAN(vlan10, vlan20, vlan30等)を作成し、互いに干渉しないようにルーティングを制御する。
  3. 確実な監視とログ収集: 不正アクセスやパケットドロップの統計を取得し、Linux Syslogサーバーに送信する。ただし、Syslogサーバーがダウンしている間はルーターの内部(NAND)にプールし、復旧後に回収できるようにする。

さらに、監視まわりでは「UP/DOWN に応じてスケジューラ自体を有効化・無効化する」のではなく、5分間隔の処理を 1 本だけ常時回し、Netwatch は状態フラグを切り替えるだけにした。これにより、制御経路が単純になり、トラブル時に追うべき箇所を減らせる。

この構成を置くネットワーク全体像

今回の前提にしているのは、CRS304 を中心にした小規模構成だ。Port1 を WAN、Port2 を Mac、Port3 を Linux サーバ、Port4 を Wi-Fi AP、Port5 を Console とし、Mac と Linux は有線 10GbE でローカル通信だけを担わせる。外向き通信は Wi-Fi 側のセグメントに逃がしつつ、ISP 側からの inbound は基本すべて drop にする。

この構成だと、RouterOS は単にルーティングをするだけではなく、ローカル転送の中核、VLAN の境界、そして監視イベントの集約点にもなる。だからこそ、syslog 送信先の Linux が落ちたときにも、RouterOS 側で最低限の統計を保持しておく価値がある。


メイン設定:ネットワーク基盤の構築

まずはWAN側の基本設定から始める。ether1をWANとして定義し、DHCPクライアントを動作させた。

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

1. DNS (DoH有効化)

DNSについては、ISPやパブリックDNSへの平文でのリクエストを防ぎ、すべて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
  

後述するファイアウォールの設定により、外向きのUDP/TCP 53番は完全禁止とし、ルーター自身はDoHのみを利用する構成としている。クライアント側は、各VLANのゲートウェイIPをDNSサーバーとして参照するようにする。

2. VLANとブリッジの設定

用途ごとにネットワークを切り離すため、vlan10, vlan20, vlan30 を作成し、各ポートに割り当てた。ether4をvlan10、ether2をvlan20、ether3をvlan30としている。

  /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サーバーの設定

今回は例として vlan10 のみにDHCPでIPアドレスを配布する構成とした。

  /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. ファイアウォール(フィルタ)

ファイアウォールルールの設計は、VLAN間の分離と、外部からの不正アクセスの遮断、そして管理用VLAN(vlan88)からのアクセス許可が中心となる。 特に forward チェーンでは、vlan10vlan20 間の通信を明示的にドロップし、LANからWANへの新規接続のみを許可している。

  /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"
  

状態連動型のSyslog・5分カウンタ送信の実装

私が今回特に工夫したのが、統計データ(ドロップカウンタ等)のロギングと監視の仕組みだ。 通常はSyslogサーバー(Linux)へUDPで常時転送するが、Syslogサーバーがメンテナンスや障害でダウンしている間に発生したドロップや攻撃の記録を取りこぼしたくなかった。

そこで、Netwatchを使ってSyslogサーバーの死活監視を行い、状態(UP/DOWN)によってログの送信先を自動的に切り替える仕組みを構築した。実装上のポイントは、Netwatch が処理本体を持たず、linux_up フラグだけを書き換えることだ。実際の処理は 5 分ごとの counter_tick に集約し、UP なら UDP syslog 送信とカウンタリセット、DOWN なら NAND 上の counter.log 追記に分岐させる。

基本ロギングと計測用DROP設定

まず、パケットドロップを計測するためのカウンタ用ルールを追加する(軽くレート制限をかけてログの氾濫を防ぐ)。

  /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

# COUNTERをsyslogへ出すため system,info も送る
/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
  

Netwatch(90秒)による状態監視

Syslogサーバー(LINUX_IP)に対して90秒間隔でPingを打ち、状態に応じてグローバル変数 linux_up のフラグを切り替えるスクリプトを仕込む。

  # 初期値: 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監視
/tool netwatch add host=LINUX_IP interval=00:01:30 up-script=netwatch_up down-script=netwatch_down
  

5分に1回のカウンタ処理スクリプト

5分間隔で動作するスケジューラを設定し、ここで変数 linux_up の状態を評価する。

  • UPの場合: カウンタ値をJSON化してSyslogへ出力し、カウンタをリセットする。
  • DOWNの場合: Syslogへ送らず、ローカルのNANDにある counter.log というファイルに追記する(リセットは行わない)。
  /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: syslog(UDP)へ流す → すぐreset ----
    /log info ("COUNTER " . $json)
    /ip firewall reset-counters
  } else={
    # ---- DOWN: NANDに1ファイルへ追記(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)
    }
  }
}

# 5分で常時回す(UP/DOWNで動きが自動分岐)
/system scheduler add name=counter_tick_5m interval=5m on-event=counter_tick
  

この形にしておくと、状態遷移時に操作対象が増えない。Netwatch 側は true/false を更新するだけでよく、実際の副作用は counter_tick 側に閉じ込められる。RouterOS の運用では「スクリプトが複数箇所で同じリソースを触る」状態を避けたかったので、この分離はかなり効いた。

また、この実装は「n分ぶんのイベントをあとから UDP で順次送り直す」方式ではない。あくまで 5 分ごとのスナップショットを送る設計で、集計の粒度を優先している。短時間のスパイクを完全再現するより、普段の運用で壊れにくいことを重視した。

復旧時のデータ回収(Linux側)

Syslogサーバーが復旧した際、NANDに溜まった過去分のカウンタログを取り込むためのスクリプトだ。Linux側からSSH経由で実行し、counter.log を回収した上で、MikroTik側から当該ファイルを削除し、カウンタをリセットする。

  #!/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
  

結果と運用

この構成により、RouterOS上でのVLANの完全なアイソレーションと、セキュアな名前解決(DoH)の強制が実現できた。 さらに監視面では、「UDP送信は手軽だが到達性が保証されない」というSyslogの欠点を、Netwatchとローカルストレージへのフォールバックによって補うことができた。この仕組みを導入したおかげで、Syslogサーバーの再起動やメンテナンスを気兼ねなく行えるようになっている。

特に今回の構成では、Mac と Linux の有線区間をローカル専用で使い、外向き通信や IoT 分離は別セグメントで扱っている。そのため、RouterOS に求めていたのは巨大な監視基盤ではなく、「最低限の異常統計を確実に残す」ことだった。counter_tick_5mcounter.log の組み合わせは、その要件に対してちょうどよかった。

今後の課題

今回の実装では、UP/DOWNの遷移が発生する瞬間に限り、数秒から数十分間のカウンタ情報が取りこぼされる可能性がある。これは90秒間隔のNetwatchと5分間隔のスケジューラの間に生じるタイムラグによるものだが、小規模環境の統計把握という目的においては許容範囲であると判断している。今後はLoki等へのダイレクトなIngestや、Prometheus/Grafanaでのより細かいメトリクス可視化(exporterの導入など)も検討していきたい。