What's new
  • SNBForums Code of Conduct

    SNBForums is a community for everyone, no matter what their level of experience.

    Please be tolerant and patient of others, especially newcomers. We are all here to share and learn!

    The rules are simple: Be patient, be nice, be helpful or be gone!

VPNMON VPNMON-R3 v1.4.1 -Jul 2, 2025- Monitor WAN/Dual-WAN/OpenVPN Health & Reset Multiple OpenVPN Connections (Now available in AMTM!) Wireguard Alpha!

Looks like the case... so I'll create an entry to save this value in order to reference it for the server list. Thanks for the sleuthing on this! :)
It was also easy enough for me to hardcode the information into the query as well. Given other VPNs changing the private key, you may not want a separate field in case we find a better way to automate the ones that do change.
 
It was also easy enough for me to hardcode the information into the query as well. Given other VPNs changing the private key, you may not want a separate field in case we find a better way to automate the ones that do change.
That's also a great way to go... I like that idea better! :)
 
Thank you for validating that, @CaptainSTX! Looks like Proton works very similarly.
Here is something else that might be interesting to you. What VPMON is reporting as my public IP is actually the end point and when I run an IP checker it is actually another IP in the same subnet.

IP STRONG VPN - AX88Pro

IP- ROUTER - VPN - STATUS - PAGE

Connected xxx.x31.87.117

Public IP xxx.x31.83.243

End Point xxx.x31.87.117

Router Client Page -

Connected to - xxx.x31.83.243

End Point Address xxx.x31.87.117


VPMONITOR

Public IP xxx.x31.87.117


WHAT IS MY IP - Web Checker

Public IP xxx.x31.87.120


All the IPs are pingable. The tracert for all is similar.
 
Last edited:
Here is something else that might be interesting to you. What VPMON is reporting as my public IP is actually the end point and when I run an IP checker it is actually another IP in the same subnet.

IP STRONG VPN - AX88Pro

IP- ROUTER - VPN - STATUS - PAGE

Connected xxx.x31.87.117

Public IP xxx.x31.83.243

End Point xxx.x31.87.117

Router Client Page -

Connected to - xxx.x31.83.243

End Point Address xxx.x31.87.117


VPMONITOR

Public IP xxx.x31.87.117


WHAT IS MY IP - Web Checker

Public IP xxx.x31.87.120


All the IPs are pingable. The tracert for all is similar.
Yeah, sorry... It's mislabeled. I still need to get the actual public ip to show up in there. ;)
 
I tried it out on my RT-AX86U PRO running 388.8_4. I have wgc1 running wich 3 devices are using and wgc2 running which no devices are using currently.

It seems vpnmon fails to verify both of these
Screenshot_20250716_180524_ConnectBot.jpg

While
Screenshot_20250716_180134_ConnectBot.jpg


Is the ping test failing? How did you circumvent the rp_filter as router itself are not set to use any of these?

Some fun facts are
- The usage of any openVPN client sets wgc rp filter to loose (or off, cant remember). Probably by global setting. I'm not using any openVPN
- On 3006 fw, atleast the later ones, rp filter is set to loose for fw requirements for determining tunnel ip. 388 fw wgcx rp filter are strict.

/Zeb

Edit:
Just for testing, setting rp filter to loose (2) for wgc1 & 2 makes it work.
Screenshot_20250716_183338_ConnectBot.jpg
 
Last edited:
Funny that these don't even match... ;)
One other thing I have noticed.
I am running WG VPN clients on slots 6 & 0 and I have set up VPMON to monitor both those slots. When I pull up the VPMON screen it doesn't give me their uptime like it does on the WAN and OpenVPN connection on slot 1.
 
Up and running @Viktor Jaep ... just monitoring my single NordVPN slot.
Will see how it goes "fighting it out" with WAN-FAILOVER.

See screenshot - just checking how your OCD is going nowadays?

;)

Screenshot 2025-07-17 at 07.03.07.png
 
Up and running @Viktor Jaep ... just monitoring my single NordVPN slot.
Will see how it goes "fighting it out" with WAN-FAILOVER.

See screenshot - just checking how your OCD is going nowadays?

;)
My OCD is alive and well, thank you very much! I'll get that fixed! :) Thanks for testing! Please let me know how it goes with WAN-FAILOVER!
 
One other thing I have noticed.
I am running WG VPN clients on slots 6 & 0 and I have set up VPMON to monitor both those slots. When I pull up the VPMON screen it doesn't give me their uptime like it does on the WAN and OpenVPN connection on slot 1.
So that's a problem I'm still trying to work out. With OVPN, VPNMON actually starts those up, so I can get an accurate start time. WG, on the other hand, starts when the router reboots. The only time you get a more accurate start time is if you reset the WG connection within VPNMON, then I can start its own timer.
 
I tried it out on my RT-AX86U PRO running 388.8_4. I have wgc1 running wich 3 devices are using and wgc2 running which no devices are using currently.

It seems vpnmon fails to verify both of these
The FAIL would be from not receiving a ping across the tunnel. I'm not using the handshake timer method to determine if the tunnel is up in this case.

Is the ping test failing? How did you circumvent the rp_filter as router itself are not set to use any of these?

Some fun facts are
- The usage of any openVPN client sets wgc rp filter to loose (or off, cant remember). Probably by global setting. I'm not using any openVPN
- On 3006 fw, atleast the later ones, rp filter is set to loose for fw requirements for determining tunnel ip. 388 fw wgcx rp filter are strict.

/Zeb

Edit:
Just for testing, setting rp filter to loose (2) for wgc1 & 2 makes it work
So this is the first I've heard of rp_filter. I checked mine, and it's set to 0.

What's the workaround to receive a ping with the rp_filter set to the strict setting you had before? Or is there no way around that? I guess we would need something other than ping to tell if the tunnel is up?
 
For Proton users comfortable with Python, you can use these two scripts to generate conf files and then a summary file to paste into VPNMON-R3 (note that you need to go to https://account.protonvpn.com/downloads to get the credential information while Developer tools is open in your browser):
Python:
import http.client
import http.cookies
import json
import base64
import hashlib
import os
import random
import argparse
import time
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import x25519
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes

"""
Copyright - FuseTim 2024

This code is dual-licensed under both the MIT License and the Apache License 2.0.

You may choose either license to govern your use of this code.

MIT License:
https://opensource.org/licenses/MIT

Apache License 2.0:
https://www.apache.org/licenses/LICENSE-2.0

By contributing to this project, you agree that your contributions will be licensed under
both the MIT License and the Apache License 2.0.
"""

######################################################################################

# Credentials (found in Headers and Cookies) for certificate file using Developer Tools
auth_server = "" # See `x-pm-uid` header
auth_token  = "" # See `AUTH-<x-pm-uid>` cookie
session_id  = "" # See `Session-Id` cookie
web_app_version = "[email protected]" # See `x-pm-appversion` header

# Settings
prefix = "VPNMON-R3" # Prefix is used for config file and name in ProtonVPN Dashboard
output_dir = "./"
selected_countries = ["CO"]
selected_cities = [ "Bogota" ]
selected_tier = 2  # 0 = Free, 2 = Plus
selected_features = [ "-TOR", "-SecureCore" ]  # Features that a server should have ("P2P", "TOR", "SecureCore", "XOR", etc) or not ("-P2P", etc)
max_servers = 30  # Maximum of generated config
max_servers_per_city = 30  # Maximum number of servers per city
listing_only = False  # Do not generate config, just list available servers with previous selectors

config_features = {
    "SafeMode": False,
    "SplitTCP": True,
    "PortForwarding": True,
    "RandomNAT": False,
    "NetShieldLevel": 0,  # 0, 1 or 2
}

######################################################################################

# Contants
connection = http.client.HTTPSConnection("account.protonvpn.com")
C = http.cookies.SimpleCookie()
C["AUTH-"+auth_server] = auth_token
C["Session-Id"] = session_id
headers = {
    "x-pm-appversion": web_app_version,
    "x-pm-uid": auth_server,
    "Accept": "application/vnd.protonmail.v1+json",
    "Cookie": C.output(attrs=[],header="", sep="; ")
}


def generateKeys():
    """Generate a client key-pair using the API. Could be generated offline but need more work..."""
    print("Generating key-pair...")
    connection.request("GET", "/api/vpn/v1/certificate/key/EC", headers=headers)
    response = connection.getresponse()
    print("Status: {} and reason: {}".format(response.status, response.reason))
    resp = json.loads(response.read().decode())
    priv = resp["PrivateKey"].split("\n")[1]
    pub = resp["PublicKey"].split("\n")[1]
    print("Key generated:")
    print("priv:", priv)
    print("pub:", pub)
    return [resp["PrivateKey"], pub, priv]


def getPubPEM(priv):
    """Return the Public key as string without headers"""
    return priv[1]

def getPrivPEM(priv):
    """Return the Private key as PKCS#8 without headers"""
    return priv[2]

def getPrivx25519(priv):
    """Return the x25519 base64-encoded private key, to be used in Wireguard config."""
    hash__ = hashlib.sha512(base64.b64decode(priv[2])[-32:]).digest()
    hash_ = list(hash__)[:32]
    hash_[0] &= 0xf8
    hash_[31] &= 0x7f
    hash_[31] |= 0x40
    new_priv = base64.b64encode(bytes(hash_)).decode()
    return new_priv


def registerConfig(server, priv):
    """Register a Wireguard configuration and return its raw response."""
    h = headers.copy()
    h["Content-Type"]= "application/json"
    print("Registering Config for server", server["Name"],"...")
    body = {
    "ClientPublicKey": getPubPEM(priv),
    "Mode": "persistent",
    "DeviceName": prefix+"-"+server["Name"],
    "Features": {
        "peerName": server["Name"],
        "peerIp": server["Servers"][0]["EntryIP"],
        "peerPublicKey": server["Servers"][0]["X25519PublicKey"],
        "platform": "Windows",
                # You can add features there (PortForwarding, SplitTCP, ModerateNAT
                # See https://github.com/ProtonMail/WebClients/blob/8b5035d6f848b76d005814fca260bb616e83a4b2/packages/components/containers/vpn/WireGuardConfigurationSection/feature.ts#L53
                "SafeMode": config_features["SafeMode"],
                "SplitTCP": config_features["SplitTCP"],
                "PortForwarding": config_features["PortForwarding"] if server["Features"] & 4 == 4 else False,
                "RandomNAT": config_features["RandomNAT"],
                "NetShieldLevel": config_features["NetShieldLevel"], # 0, 1 or 2
    }
    }
    connection.request("POST", "/api/vpn/v1/certificate", body=json.dumps(body), headers=h)
    response = connection.getresponse()
    print("Status: {} and reason: {}".format(response.status, response.reason))
    resp = json.loads(response.read().decode())
    print(resp)
    return resp

def generateConfig(priv, register):
    """Generate a Wireguard config using the ProtonVPN API answer."""
    conf = """[Interface]
# Key for {prefix}
PrivateKey = {priv}
Address = 10.2.0.2/32
DNS = 10.2.0.1

[Peer]
# {server_name}
PublicKey = {server_pub}
AllowedIPs = 0.0.0.0/0
Endpoint = {server_endpoint}:51820
    """.format(prefix=prefix, priv=getPrivx25519(priv), server_name=register["Features"]["peerName"], server_pub=register["Features"]["peerPublicKey"], server_endpoint=register["Features"]["peerIp"])
    return conf


def write_config_to_disk(name, conf):
    f = open(output_dir+"/"+name+".conf", "w")
    f.write(conf)
    f.close()


# VPN Listings

connection.request("GET", "/api/vpn/logicals", headers=headers)
response = connection.getresponse()
print("Status: {} and reason: {}".format(response.status, response.reason))

servers = json.loads(response.read().decode())["LogicalServers"]

for s in servers:
    feat = [
    "SecureCore" if s["Features"] & 1 == 1 else "-SecureCore",
    "TOR" if s["Features"] & 2 == 2 else "-TOR",
    "P2P" if s["Features"] & 4 == 4 else "-P2P",
    "XOR" if s["Features"] & 8 == 8 else "-XOR",
    "IPv6" if s["Features"] & 16 == 16 else "-IPv6"
    ]
    if (not s["EntryCountry"] in selected_countries and not s["ExitCountry"] in selected_countries) or s["Tier"] != selected_tier:
        continue
    if len(list(filter(lambda sf: not (sf in feat), selected_features))) > 0:
        continue
    print("- Server", s["Name"])
    print("  > ID:", s["ID"])
    print("  > EntryCountry:", s["EntryCountry"])
    print("  > ExitCountry:", s["ExitCountry"])
    print("  > Tier:", s["Tier"])
    print("  > Features:")
    print("      - SecureCore:", "Y" if s["Features"] & 1 == 1 else "N")
    print("      - Tor:", "Y" if s["Features"] & 2 == 2 else "N")
    print("      - P2P:", "Y" if s["Features"] & 4 == 4 else "N")
    print("      - XOR:", "Y" if s["Features"] & 8 == 8 else "N")
    print("      - IPv6:", "Y" if s["Features"] & 16 == 16 else "N")
    print("  > Score:", s["Score"])
    print("  > Load:", s["Load"])
    print("  > Status:", s["Status"])
    print("  > Instance:")
    for i in s["Servers"]:
        print("    - Instance n°",i["Label"],":", i["ID"])
        print("      > EntryIP:", i["EntryIP"])
        print("      > ExitIP:", i["ExitIP"])
        print("      > Domain:", i["Domain"])
        print("      > X25519PublicKey:", i["X25519PublicKey"])
    if not listing_only:
        keys = generateKeys()
        reg = registerConfig(s, keys)
        config = generateConfig(keys, reg)
        write_config_to_disk(reg["DeviceName"], config)
        max_servers-=1
    if (max_servers <= 0):
        break
    time.sleep(5)

connection.close()

After the files are generated:
Bash:
#!/usr/bin/env bash

PREFIX="Proton"

for f in *.conf; do
  # Extract line 8, remove leading '# ' and trim
  name=$(sed -n '8s/^# *//p' "$f" | xargs)
  # Extract Endpoint, replace : with ,
  endpoint=$(grep -m1 '^Endpoint' "$f" | awk '{print $3}' | sed 's/:/,/')
  # Extract PrivateKey (first occurrence)
  privkey=$(grep -m1 '^PrivateKey' "$f" | awk '{print $3}')
  # Extract PublicKey (first occurrence)
  pubkey=$(grep -m1 '^PublicKey' "$f" | awk '{print $3}')
  # Output as CSV line
  echo "$PREFIX $name,$endpoint,$privkey,$pubkey"
done > summary.txt

Mighty fine detective work!

Looks like the case... so I'll create an entry to save this value in order to reference it for the server list. Thanks for the sleuthing on this! :)

I think we need to hire this guy! He will get paid in "likes" just like us.
 
The FAIL would be from not receiving a ping across the tunnel. I'm not using the handshake timer method to determine if the tunnel is up in this case.


So this is the first I've heard of rp_filter. I checked mine, and it's set to 0.

What's the workaround to receive a ping with the rp_filter set to the strict setting you had before? Or is there no way around that? I guess we would need something other than ping to tell if the tunnel is up?
or you modify the rp_filter temporarily until your ping is done. How often do you anticipate running ping, or is that a constant feature relied on?

here are two options:

Code:
# Route ping replies properly via policy routing
ip rule add from <vpn_ip> lookup <vpn_table>
ip route add default via <vpn_gateway> dev tunX table <vpn_table>

# Or temporarily relax rp_filter
echo 2 > /proc/sys/net/ipv4/conf/tunX/rp_filter

keep in mind you would have to substitute "wg" for the "tun"
 
Last edited:
My OCD is alive and well, thank you very much! I'll get that fixed! :) Thanks for testing! Please let me know how it goes with WAN-FAILOVER!

@Viktor Jaep glad to hear your OCD is healthy and happy - I think you know we share a common bond there! 😁

Based on some extra emails I'm getting from wan-failover (since re-installing vpnmon-r3) on a reboot, where it senses WAN0 is disconnected and then reconnects shortly after, I think there may be some kind of "tussle" going on but it's maybe a startup timing issue between the two scripts?

I might try playing with the respective delays of starting up VPNMON-R3 and WAN-FAILOVER and see if I can make it play nice, or do you have any other thoughts?
Your script doesn't restart the WAN interface does it? Trying to work out which should "settle" first ...

I have your "Provide additional WAN/Dual WAN monitoring" option enabled but what exactly does that do? Could that be playing into it?

Cheers and hope you are well!

StephenH
 
@Viktor Jaep glad to hear your OCD is healthy and happy - I think you know we share a common bond there! 😁

Based on some extra emails I'm getting from wan-failover (since re-installing vpnmon-r3) on a reboot, where it senses WAN0 is disconnected and then reconnects shortly after, I think there may be some kind of "tussle" going on but it's maybe a startup timing issue between the two scripts?

I might try playing with the respective delays of starting up VPNMON-R3 and WAN-FAILOVER and see if I can make it play nice, or do you have any other thoughts?
Your script doesn't restart the WAN interface does it? Trying to work out which should "settle" first ...

I have your "Provide additional WAN/Dual WAN monitoring" option enabled but what exactly does that do? Could that be playing into it?

Cheers and hope you are well!

StephenH
Hey Stephen I see you are keeping everyone engaged! Just letting you know if you do decide to change your main router -- I got it working with Guest network pro. :cool: I thought you would be happy to know there is a safety net when you or if you take that plunge into router mania.
 
I see you are keeping everyone engaged!
Hopefully engaged - but I tend to find edge cases that get poor script authors like @Viktor Jaep and yourself charging off down blind alleys ...
Apologies in advance for any past, present and future sins!

:oops:
 

Similar threads

Latest threads

Support SNBForums w/ Amazon

If you'd like to support SNBForums, just use this link and buy anything on Amazon. Thanks!

Sign Up For SNBForums Daily Digest

Get an update of what's new every day delivered to your mailbox. Sign up here!

Staff online

Back
Top