Community discussions

MikroTik App
 
hpram99
just joined
Topic Author
Posts: 2
Joined: Sun Nov 05, 2017 7:08 pm

Script: NordVPN Auto-updater, multiple tunnels, load checking, and more!

Thu Jul 06, 2023 6:16 pm

I made this script to keep multiple NordVPN tunnels up on optimal peers with minimal impact so that I can load-balance across them.
I use 3 tunnels, but Nord allows up to 6 concurrent connections.

With this script I'm able to load-balance and get 800mb/s on a 1gb internet at all times.
RB5009 shows about 70% CPU utilization with 3 PCC markings for load balancing, among all it's other tasks.

Features:
  • Broken into phases so specific phases can be used individually in other scripts
  • Configurable Peer Name, and # of tunnels you would like to replace (replace only 2 at a time, when 3 are configured)
  • Load check of existing peers, configurable load threshold (do not replace if existing peer is <20% load)
  • Configurable country and server features (P2P, Double VPN, etc)
  • Configurable Search Strings and API URL's - potentially useful for other VPN service providers
  • Identifies if the API provided any identical Peers and removes them so as not to cause unneeded reconnection
Configuration:
  • Tunnels must be configured already, and follow [NAME][1-9] format even if you use only 1 tunnel. (NordVPN1, NordVPN2, NordVPN3)
  • $VPNPeerName = name without the number
  • $NewVPNLimit = optional. default max 9, can be set to a lower number than you have peers configured, note: candidate selection is not random, it is sequential starting at 1. - Currently this is best for keeping an always-up peer for failover that normally doesn't change.
  • $LoadThreshold = Max allowable load percentage of existing peers. If a peer is found to have a load lower than this, it will not be a candidate for replacement.
  • $VPNCountry $VPNServerType and $VPNProtocol = VPN server selection parameters. Country 228 = USA
  • Set a schedule to run every x hours, for me, checking every 4 hours at 20% load replaces a peer about once per day.
Known Caveats:
  • Nord occasionally has bad server suggestions and new VPN will be down, there are other scripts that can be used to check up/down status.
  • No validation of Peers received from API, possibility that a valid Peer configuration is replaced with garbage if the API changes.
  • RouterOS variable size limit might be an issue with larger API results for larger peer counts.
{
:local VPNPeerName   "NordVPN"
:local NewVPNLimit   9
:local LoadThreshold 20

:local VPNCountry    "228"
:local VPNServerType "legacy_standard"
:local VPNProtocol   "ikev2"

:local ServersStart  "\"hostname\":\""
:local ServersEnd    "\""
:local StatsStart    "percent\":"
:local StatsEnd      "}"
:local ServersURL    "https://api.nordvpn.com/v1/servers/recommendations"
:local StatsURL      "https://api.nordvpn.com/server/stats"

:local apiquery ""
:local data ""
:local dataindex 1
:local dataindexend 1
:local OldVPNPeers [:toarray ""]
:local NewVPNPeers [:toarray ""]
:local OldVPNPeerstoReplace [:toarray ""]
:local NewVPNPeerReplacement [:toarray ""]

:local load 0
:local OldPeerCount 0
:local FullEntryName ""
:local OldPeerAddress ""
:local exitloop false
:local found false
:local i 0

#######################################################
:put ("Phase 1 - find and count old Peer Names")
#######################################################

:set OldPeerCount [/ip ipsec peer print count-only where name~"$VPNPeerName\\d"]
:if ($NewVPNLimit > $OldPeerCount) do={
  :set NewVPNLimit $OldPeerCount
}

:set $exitloop false
:set i 0
:while (($i <= $OldPeerCount) && (!$exitloop)) do={
  :set i ($i + 1)
  :set FullEntryName ($VPNPeerName . $i)
  :if ([:len [/ip ipsec peer find name=$FullEntryName]] > 0) do={
    :set OldPeerAddress [/ip ipsec peer get $FullEntryName address]
    :set OldVPNPeers ($OldVPNPeers, $OldPeerAddress)
  } else={
    :set $exitloop true
  }
}
:put ("Old VPN Names: " . [:tostr $OldVPNPeers])

#######################################################
:put ("Phase 2 - Download new VPN list from API with: country id $VPNCountry, server group $VPNServerType and technology $VPNProtocol")
#######################################################

:set apiquery "limit=$NewVPNLimit&filters[country_id]=$VPNCountry&filters[servers_groups][identifier]=$VPNServerType&filters[servers_technologies][identifier]=$VPNProtocol"
:put ("$ServersURL\3F$apiquery")
### must be $data - RouterOS syntax requirements
:set data ([/tool fetch url="$ServersURL\3F$apiquery" output=user as-value ]->"data")

#######################################################
:put ("Phase 3 - Parse API results, make array of new Peers")
#######################################################

:if ([:typeof $data] = "nil") do={
  :log info "There was an issue downloading VPN list from NordVPN"
} else={
  :set $i 0
  :while ($i < $NewVPNLimit) do={
  	:set dataindex [:find $data $ServersStart ($dataindex -1)]
  	:set dataindex ($dataindex + [:len $ServersStart])
  	:set dataindexend [:find $data $ServersEnd $dataindex]
  	:set ($NewVPNPeers->$i) [:pick $data $dataindex [$dataindexend - $dataindex]]
  	:set i ($i + 1)
  	:put "Iteration $i found index $dataindex"
  }

  :put ("Array Data to NewVPNPeers: " . [:tostr $NewVPNPeers])
}

#######################################################
:put ("Phase 4 - Discard URLs that match existing Peer config, create Array of Unique new URLs and old Peers to update")
#######################################################

:foreach new in=$NewVPNPeers do={
  :set found false
  :foreach old in=$OldVPNPeers do={
    :if ($new = $old) do={
      :set found true
    }
  }
  :if ($found = false) do={
    :set NewVPNPeerReplacement ($NewVPNPeerReplacement, $new)
  }
}
:foreach old in=$OldVPNPeers do={
  :set found false
  :foreach new in=$NewVPNPeers do={
    :if ($old = $new) do={
      :set found true
    }
  }
  :if ($found = false) do={
    :set OldVPNPeerstoReplace ($OldVPNPeerstoReplace, $old)
  }
}

:put ("NewVPNPeerReplacement values:" . [:tostr  $NewVPNPeerReplacement])
:put ("OldVPNPeerstoReplace values :" . [:tostr  $OldVPNPeerstoReplace])

#######################################################
:put ("Phase 5 - Check load and Update old Peers with new URLS if needed")
#######################################################

:set $i 0
:foreach newVPN in=$NewVPNPeerReplacement do={
  :set $dataindex 1
  :set OldPeerAddress ($OldVPNPeerstoReplace->[$i])
  :set apiquery $OldPeerAddress
  :set data ([/tool fetch url="$StatsURL/$apiquery" output=user as-value ]->"data")
  :set dataindex [:find $data $StatsStart ($dataindex -1)]
  :set dataindex ($dataindex + [:len $StatsStart])
  :set dataindexend [:find $data $StatsEnd $dataindex]
  :set load [:pick $data $dataindex [$dataindexend - $dataindex]]
  :put ("$OldPeerAddress Load:$load")
  :if ($load > $LoadThreshold ) do={
    :put "ip ipsec peer set $OldPeerAddress address=$newVPN"
    /ip ipsec peer set [find address=$OldPeerAddress] address=$newVPN
    :delay 4000ms
  }
  :set $i ($i + 1)
}

}

Who is online

Users browsing this forum: No registered users and 11 guests