Monitor OpenVPN Client Activity using Python

09 Aug 2020 linux

Installing OpenVPN in your own server gives you two major benefits. First, you will have better privacy control. Second, no need to spend on pay-per-user thing. As long as the CPU can handle encryption/decryption, and the ethernet controller can handle the traffic, OpenVPN is a pretty good choice for providing VPN service to a small-sized team.

OpenVPN Config

Anyway, a typical OpenVPN Server configuration looks like the following.

dev tun-udp-1194
proto udp
port 1194

ca /etc/openvpn/pki/ca.crt
cert /etc/openvpn/pki/issued/
key /etc/openvpn/pki/private/
dh /etc/openvpn/pki/dh.pem
crl-verify /etc/openvpn/pki/crl.pem

remote-cert-tls client
keepalive 10 120
tls-auth /etc/openvpn/pki/ta.key 0
cipher AES-256-CBC
tls-cipher TLS-DHE-DSS-WITH-AES-256-CBC-SHA256
auth SHA256
tls-version-min 1.2
verb 0

OpenVPN Management over Telnet

In order to access the telnet management console, the following line has to be added in /etc/openvpn/server.conf. Also the openvpn.service must be reloaded for the change to take effect.

management 7000

Alright. Next thing is, try connecting to port 7000 via telnet command. If it was a success, then we can use a small Python3 script to talk with the OpenVPN server.

A Bit of Knowledge about Common Name

Before we run any script, we need to find out the CN or Common Name used in the RSA Root Certificate. For example, if you generate the CA file with, both EasyRSA and OpenVPN will append this CN after the client’s name. A sample server and client names could be as follows.

Client Mark:
Client Bob:
Client Alice:

Which means, the client names must be unique because OpenVPN uses the client name to map DHCP IP Addresses for remote users.

Run the Script!

Well, now we know the common name. It could be, or or server or anything. We need to amend the RE_COMMON_NAME regex and put the correct value there. Once done, upload the script to the OpenVPN server and run it using python3.

#!/usr/bin/env python3

import re
import telnetlib
import time

# TODO change here

HOST = ""
PORT = "7000"

def alert(user, ip, state):
    print(F"*{user}* {state} from {ip}")
    # TODO You can call Slack or API Gateway here
while True:
    telnet = telnetlib.Telnet(HOST,PORT, 5)
    output = telnet.read_until("END\n", 1)
    CURR_USERS = {}
    for line in output.split("\n"):
        if, line):
            user, realaddress, _, _, _ = line.split(",")
            ip = realaddress.split(":")[0]
            CURR_USERS[user] = ip
    connected = set(CURR_USERS.keys()) - set(PRE_USERS.keys())
    disconnected = set(PRE_USERS.keys()) - set(CURR_USERS.keys())
    for user in list(connected):
        alert(user, CURR_USERS[user], "connected")
    for user in list(disconnected):
        alert(user, PRE_USERS[user], "disconnected")

The Result

If you keep running the script in background (screen or systemd whichever you prefer), you will see activity alerts like the following. connected from connected from connected from
... disconnected from disconnected from disconnected from

More Tweaks

You can simply override the alert function and call custom Slack or API Gateway URL using requests. Tada!