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
- 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.
- 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.
Code: Select all
{
: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)
}
}