RouterOS:MikroTik設定WireGuard VPN

前陣子剛好有需要透過家裡的VPN進行一些工作,這篇文章在此紀錄在MikroTik路由器裡面設定WireGuard VPN的過程。在這裡我全程都是以WebFig介面的角度出來撰寫。

MikroTik WireGuard VPN規劃與設定

Step 1. 設定DDNS

除非你去申請固定IP,否則就需要設定DDNS來讓每次你要連回家的時候,都知道要連線到哪個IP位址。

Step 2. 建立WireGuard介面

Step 3. 設定VPN介面IP地址

Step 4. 設定防火牆

** 允許外部WireGuard連入 **

** 允許VPN流量存取內網 **

✅ 重要:記得要把這兩條規則都拖曳到防火牆的頂端。

Step 5. WireGuard客戶端設定

[Interface]
PrivateKey = 自動產生的私鑰
Address = 10.0.0.2/32
DNS = 10.0.0.1

[Peer]
PublicKey = ServerPublic123
# 流量模式切換
# Mode A(全流模式:翻牆):
AllowedIPs = 0.0.0.0/0
# Mode B(分流模式:僅連內網):
# AllowedIPs = 10.0.0.0/24, 192.168.2.0/24

Endpoint = slashview.sn.mynetname.net:51820
PersistentKeepalive = 25

Step 6. 回到路由器設定Peer

做到這裡你就會明白為何WireGuard安全如此的高了,就是因為他連鑰匙都沒辦法透過遠端傳輸,你必須要事先在雙端路由器上面設定好Public Key這樣才能建立連線,這種毫無彈性的工程師思維真是廢到想笑。(所以這也意味著你最好把Client端設定檔好好保留起來)

Step 7. 回到WireGuard客戶端測試連線狀況

補充:網路開機封包

有時候你需要在VPN連線的狀態下,從外部透過網路開機封包啟動家裡的電腦,透過MikroTik路由器內建的Tools > WOL功能需要實際去背誦MAC位址顯得很搞笑之外,如果我們想要透過程式碼針對記憶在程式碼裡面的MAC發送網路開機封包一樣辦不到,因為VPN IP(10.0.0.xx)與內網IP(192.168.2.xx)先天的限制,導致沒有辦法跨越網段發送MAC封包

這時候我們可以透過在路由器上面設定一個靜態ARP來讓路由器透過某內網IP對應廣播MAC位址,這樣就可以透過VPN IP來轉發網路開機封包了。我們可以從下列路徑進入設定:IP > ARP > Add New,設定如下圖之屬性:

透過上圖的規則,搭配Step 4設定的跨網段防火牆forward轉發,就可以針對例如192.168.2.254內網IP發送網路開機封包,他會幫我們把封包廣播到FF:FF:FF:FF:FF:FF全部內網,自然程式碼中對應的MAC機器就可以收到這組封包了。

發送封包的程式碼可以參考下方Powershell(採用NerdFont字形):

try {
  [System.Console]::CursorVisible = $false

  $aryHosts = @(
    [pscustomobject]@{cName = "SomePC via LAN"; cMac = "xx-xx-xx-xx-xx-xx"; cIcon = ""; cIP = "255.255.255.255"}
    [pscustomobject]@{cName = "SomePC via VPN"; cMac = "xx-xx-xx-xx-xx-xx"; cIcon = ""; cIP = "192.168.2.254"}
  )
  
  $aryMenuItems = @($aryHosts)
  $aryMenuItems += [pscustomobject]@{cName = "Exit"; cMac = "EXIT"; cIcon = "󰈆"; cIP = ""}

  function GetLocalIp {
    try {
      $oUdp = [System.Net.Sockets.UdpClient]::new()
      $oUdp.Connect("8.8.8.8", 53)
      $oEndPoint = $oUdp.Client.LocalEndPoint -as [System.Net.IPEndPoint]
      return $oEndPoint.Address.ToString()
    }
    catch {
      return "Unknown"
    }
    finally {
      if ($oUdp -ne $null) {
        $oUdp.Close()
        $oUdp.Dispose()
      }
    }
  }

  function SendWakeOnLan {
    param(
      [Parameter(Mandatory=$true)]
      [string]$cMac,
      [Parameter(Mandatory=$true)]
      [string]$cIP
    )
    
    $aryMacByte = $cMac.Split("-:") | ForEach-Object { [System.Convert]::ToByte($_, 16) }
    $aryPacket = [byte[]](@(0xff) * 6 + $aryMacByte * 16)
    $cLocalIP = GetLocalIp
    
    $oUdp = New-Object System.Net.Sockets.UdpClient
    $oUdp.Connect($cIP, 9)
    [void]$oUdp.Send($aryPacket, $aryPacket.Length)
    $oUdp.Close()
    
    Write-Host ""
    Write-Host "    Magic Packet has been sent from " -ForegroundColor Green -NoNewline
    Write-Host $cLocalIP -ForegroundColor Yellow -NoNewline
    Write-Host " to " -ForegroundColor Green -NoNewline
    Write-Host $cIP -ForegroundColor Yellow
    Write-Host "   󱫋 MAC: " -ForegroundColor Green -NoNewline
    Write-Host $cMac -ForegroundColor Cyan
  }

  $iSelectedIndex = 0

  while ($true) {
    Clear-Host
    Write-Host "╭─────────────────────────────────────╮" -ForegroundColor Cyan
    Write-Host "│" -ForegroundColor Cyan -NoNewline
    Write-Host "      WOL Remote Boot Management    " -ForegroundColor White -NoNewline
    Write-Host "│" -ForegroundColor Cyan
    Write-Host "╰─────────────────────────────────────╯" -ForegroundColor Cyan

    for ($iIndex = 0; $iIndex -lt $aryMenuItems.Count; $iIndex++) {
      if ($aryMenuItems[$iIndex].cMac -eq "EXIT") {
        Write-Host ""
      }

      $cIconPart = " $($aryMenuItems[$iIndex].cIcon)  "
      $cNamePart = "$($aryMenuItems[$iIndex].cName) ".PadRight(33)
      
      if ($iIndex -eq $iSelectedIndex) {
        Write-Host " " -ForegroundColor Yellow -NoNewline
        Write-Host $cIconPart -BackgroundColor DarkCyan -ForegroundColor Yellow -NoNewline
        Write-Host $cNamePart -BackgroundColor DarkCyan -ForegroundColor White
      } else {
        Write-Host "  " -NoNewline
        Write-Host $cIconPart -ForegroundColor Yellow -NoNewline
        Write-Host $cNamePart -ForegroundColor Gray
      }
    }

    Write-Host "───────────────────────────────────────" -ForegroundColor DarkGray
    Write-Host "   Use    to move, Enter to select" -ForegroundColor DarkGray
    
    if ($oKey = [System.Console]::ReadKey($true)) {
      if ($oKey.Key -eq [System.ConsoleKey]::UpArrow) {
        $iSelectedIndex--
        if ($iSelectedIndex -lt 0) { $iSelectedIndex = $aryMenuItems.Count - 1 }
      } elseif ($oKey.Key -eq [System.ConsoleKey]::DownArrow) {
        $iSelectedIndex++
        if ($iSelectedIndex -ge $aryMenuItems.Count) { $iSelectedIndex = 0 }
      } elseif ($oKey.Key -eq [System.ConsoleKey]::Enter) {
        $oTarget = $aryMenuItems[$iSelectedIndex]
        
        if ($oTarget.cMac -eq "EXIT") {
          Write-Host ""
          Write-Host "   󰈆 Program terminated." -ForegroundColor DarkGray
          break
        } else {
          Write-Host ""
          Write-Host "    Waking up: $($oTarget.cName)..." -ForegroundColor Red
          SendWakeOnLan -cMac $oTarget.cMac -cIP $oTarget.cIP
          Start-Sleep -Seconds 20
        }
      }
    }
  }
}
catch {
  Write-Host ""
  Write-Host "    An error occurred during execution:" -ForegroundColor Red
  Write-Host "  $($_.Exception.Message)" -ForegroundColor Red
  Write-Host ""
  Read-Host "   Press Enter to exit"
}
finally {
  [System.Console]::CursorVisible = $true
}

相關參考

MikroTik RouterOs WireGuardVpn WebFig VpnServer SetupDdns VpnInterface VpnIpAddress FirewallRules ClientSetup PublicKey PrivateKey PeerConfiguration TrafficRouting ConnectionTesting