OpenVPN Bant Genişliği İzleme ve Raporlama

VPN altyapınızı kurdunuz, kullanıcılar bağlanıyor, tünel çalışıyor. Peki kim ne kadar bant genişliği kullanıyor? Hangi saat diliminde trafik zirvesi yaşanıyor? Bir kullanıcı aniden 50GB veri indirirse bunu fark edebiliyor musunuz? Bu sorulara cevap veremiyorsanız, OpenVPN kurulumunuz yarım kalmış demektir.

Bant genişliği izleme, sysadmin işinin en çok ertelenen ama en kritik parçalarından biridir. “Şimdilik çalışıyor” mantığıyla kurulan sistemler, kapasite planlaması yapılmadığı için bir gün aniden çöker ya da faturalar patlar. Bu yazıda OpenVPN ortamında bant genişliği izlemeyi ve raporlamayı uçtan uca ele alacağız.

OpenVPN’in Yerleşik İstatistik Mekanizması

OpenVPN, kurulu geldiğinde bile temel istatistik bilgilerini sunabiliyor. Ancak bu mekanizmayı aktif hale getirmek gerekiyor.

Status Dosyası ile Anlık İzleme

/etc/openvpn/server.conf dosyanıza şu satırları ekleyin:

status /var/log/openvpn/openvpn-status.log 30
status-version 2
log-append /var/log/openvpn/openvpn.log
verb 3

Burada status direktifi 30 saniyede bir /var/log/openvpn/openvpn-status.log dosyasını güncelliyor. status-version 2 ile daha ayrıntılı çıktı alıyoruz.

Servis yeniden başlatıldıktan sonra status dosyasına bakın:

sudo systemctl restart openvpn@server
cat /var/log/openvpn/openvpn-status.log

Çıktı şuna benzer bir şey gösterecek:

TITLE,OpenVPN 2.5.9 x86_64-pc-linux-gnu
TIME,2024-01-15 14:23:11,1705321391
HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,Virtual IPv6 Address,Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t),Username,Client ID,Peer ID
CLIENT_LIST,ahmet.yilmaz,192.168.1.45:51234,10.8.0.2,,1048576,2097152,2024-01-15 13:00:00,1705316400,ahmet.yilmaz,1,0
CLIENT_LIST,mehmet.kaya,85.24.33.12:44821,10.8.0.6,,524288,1048576,2024-01-15 14:10:00,1705320600,mehmet.kaya,2,1
HEADER,ROUTING_TABLE,Virtual Address,Common Name,Real Address,Last Ref,Last Ref (time_t)
ROUTING_TABLE,10.8.0.2,ahmet.yilmaz,192.168.1.45:51234,2024-01-15 14:23:05,1705321385
GLOBAL_STATS,Max bcast/mcast queue length,0
END

Bu dosyayı parse eden basit bir bash scripti yazalım:

#!/bin/bash
# /usr/local/bin/vpn-bandwidth-check.sh

STATUS_FILE="/var/log/openvpn/openvpn-status.log"
LOG_FILE="/var/log/openvpn/bandwidth-history.csv"

# CSV başlığı yoksa oluştur
if [ ! -f "$LOG_FILE" ]; then
    echo "timestamp,username,real_ip,virtual_ip,bytes_received,bytes_sent,connected_since" > "$LOG_FILE"
fi

TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

grep "^CLIENT_LIST," "$STATUS_FILE" | while IFS=',' read -r header cn real_addr virt_addr virt_ipv6 bytes_rx bytes_tx connected_since rest; do
    echo "$TIMESTAMP,$cn,$real_addr,$virt_addr,$bytes_rx,$bytes_tx,$connected_since" >> "$LOG_FILE"
done

echo "[$TIMESTAMP] Bandwidth snapshot alindi." >> /var/log/openvpn/vpn-monitor.log

Bu scripti her 5 dakikada bir çalıştırmak için cron’a ekleyin:

chmod +x /usr/local/bin/vpn-bandwidth-check.sh
crontab -e
# Şu satırı ekleyin:
# */5 * * * * /usr/local/bin/vpn-bandwidth-check.sh

Management Interface ile Gerçek Zamanlı İzleme

OpenVPN’in management arayüzü, anlık veri almak için çok daha güçlü bir yöntem. Önce server.conf dosyasına management direktifini ekleyelim:

management 127.0.0.1 7505
management-hold

Güvenlik açısından management arayüzünü sadece localhost’a bağlamak kritik. Şimdi telnet ya da netcat ile bağlanıp anlık veri çekelim:

# Bağlantı testi
echo "status 2" | nc -q 1 127.0.0.1 7505

# Daha kullanışlı bir komut:
(echo "status 2"; sleep 1; echo "quit") | nc 127.0.0.1 7505

Bu management interface’i kullanan bir Python scripti çok daha işlevsel olacak:

#!/usr/bin/env python3
# /usr/local/bin/vpn_monitor.py

import socket
import time
import json
import datetime
import os

MGMT_HOST = '127.0.0.1'
MGMT_PORT = 7505
OUTPUT_JSON = '/var/log/openvpn/vpn_stats.json'

def get_vpn_status():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((MGMT_HOST, MGMT_PORT))
        s.settimeout(5)

        # Karşılama mesajını oku
        data = b""
        while True:
            try:
                chunk = s.recv(4096)
                if not chunk:
                    break
                data += chunk
                if b">" in chunk:
                    break
            except socket.timeout:
                break

        # Status komutunu gönder
        s.send(b"status 2n")
        time.sleep(0.5)

        response = b""
        while True:
            try:
                chunk = s.recv(4096)
                if not chunk:
                    break
                response += chunk
                if b"END" in chunk:
                    break
            except socket.timeout:
                break

        s.send(b"quitn")
        s.close()

        return parse_status(response.decode('utf-8', errors='replace'))

    except Exception as e:
        print(f"Hata: {e}")
        return None

def parse_status(raw):
    clients = []
    lines = raw.strip().split('n')

    for line in lines:
        if line.startswith('CLIENT_LIST,'):
            parts = line.split(',')
            if len(parts) >= 9:
                client = {
                    'username': parts[1],
                    'real_address': parts[2],
                    'virtual_address': parts[3],
                    'bytes_received': int(parts[5]) if parts[5].isdigit() else 0,
                    'bytes_sent': int(parts[6]) if parts[6].isdigit() else 0,
                    'connected_since': parts[7],
                    'bytes_received_mb': round(int(parts[5]) / 1024 / 1024, 2) if parts[5].isdigit() else 0,
                    'bytes_sent_mb': round(int(parts[6]) / 1024 / 1024, 2) if parts[6].isdigit() else 0,
                }
                clients.append(client)

    return {
        'timestamp': datetime.datetime.now().isoformat(),
        'active_clients': len(clients),
        'clients': clients,
        'total_rx_mb': round(sum(c['bytes_received_mb'] for c in clients), 2),
        'total_tx_mb': round(sum(c['bytes_sent_mb'] for c in clients), 2),
    }

if __name__ == '__main__':
    stats = get_vpn_status()
    if stats:
        with open(OUTPUT_JSON, 'w') as f:
            json.dump(stats, f, indent=2, ensure_ascii=False)
        print(json.dumps(stats, indent=2, ensure_ascii=False))
    else:
        print("VPN istatistikleri alinamadi.")

Scripti çalıştırıp test edin:

chmod +x /usr/local/bin/vpn_monitor.py
python3 /usr/local/bin/vpn_monitor.py

iftop ve vnstat ile Arayüz Bazlı İzleme

OpenVPN tüneli bir ağ arayüzü olarak görünür (tun0 ya da tun1 gibi). Bu arayüzü doğrudan izlemek çok değerli veriler verir.

vnstat ile kalıcı bant genişliği kaydı tutmak için:

# vnstat kurulumu
apt install vnstat -y   # Debian/Ubuntu
yum install vnstat -y   # CentOS/RHEL

# tun0 arayüzünü izlemeye başla
vnstat -i tun0 --add
systemctl restart vnstat

# Günlük rapor
vnstat -i tun0 -d

# Saatlik rapor
vnstat -i tun0 -h

# Aylık özet
vnstat -i tun0 -m

# Canlı trafik izleme
vnstat -i tun0 -l

vnstat verilerini bir bash scriptiyle günlük e-posta raporuna çevirebilirsiniz:

#!/bin/bash
# /usr/local/bin/vpn-daily-report.sh

REPORT_DATE=$(date '+%Y-%m-%d')
REPORT_FILE="/tmp/vpn-report-${REPORT_DATE}.txt"
ADMIN_MAIL="[email protected]"
VPN_IFACE="tun0"

cat > "$REPORT_FILE" << EOF
OpenVPN Gunluk Bant Genisligi Raporu
Tarih: $REPORT_DATE
Sunucu: $(hostname)
======================================

=== ARAYUZ ISTATISTIKLERI (vnstat) ===
$(vnstat -i $VPN_IFACE -d 2>/dev/null || echo "vnstat verisi bulunamadi")

=== AKTIF KULLANICILAR ===
$(cat /var/log/openvpn/vpn_stats.json 2>/dev/null | python3 -c "
import json, sys
try:
    data = json.load(sys.stdin)
    print(f'Aktif kullanici sayisi: {data["active_clients"]}')
    print(f'Toplam indirilen: {data["total_rx_mb"]} MB')
    print(f'Toplam yuklenen: {data["total_tx_mb"]} MB')
    print()
    for c in data['clients']:
        print(f'  {c["username"]:30} IN: {c["bytes_received_mb"]:8.2f} MB  OUT: {c["bytes_sent_mb"]:8.2f} MB')
except:
    print('JSON parse hatasi')
")

=== SON 24 SAAT LOG OZETI ===
Toplam baglanti sayisi: $(grep "$(date '+%Y-%m-%d')" /var/log/openvpn/openvpn.log 2>/dev/null | grep -c "Peer Connection Initiated" || echo "0")
Basarili kimlik dogrulama: $(grep "$(date '+%Y-%m-%d')" /var/log/openvpn/openvpn.log 2>/dev/null | grep -c "Authenticated" || echo "0")

EOF

# Mail gönder (mailutils kurulu olmalı)
if command -v mail &> /dev/null; then
    mail -s "OpenVPN Gunluk Raporu - $REPORT_DATE" "$ADMIN_MAIL" < "$REPORT_FILE"
    echo "Rapor gonderildi: $ADMIN_MAIL"
else
    cat "$REPORT_FILE"
fi

# Raporu arşivle
cp "$REPORT_FILE" "/var/log/openvpn/reports/"

Kullanıcı Bazlı Kota Sistemi Kurma

Gerçek dünyada bir müşterinin ya da çalışanın bant genişliğini sınırlamak gerekebilir. OpenVPN’in kendi kota sistemi yok ama bu işi script ve tc (traffic control) ile halledebiliriz.

#!/bin/bash
# /usr/local/bin/vpn-quota-check.sh
# Bu script client-connect hook olarak çalışır

# OpenVPN client-connect script'i için server.conf'a ekleyin:
# script-security 2
# client-connect /usr/local/bin/vpn-quota-check.sh

USERNAME="$common_name"
QUOTA_FILE="/etc/openvpn/quotas/${USERNAME}.quota"
USAGE_FILE="/var/log/openvpn/usage/${USERNAME}.usage"
MONTHLY_LIMIT_GB=50  # Varsayılan limit: 50GB

# Kullanıcıya özel limit varsa oku
if [ -f "$QUOTA_FILE" ]; then
    MONTHLY_LIMIT_GB=$(cat "$QUOTA_FILE")
fi

# Bu ayın kullanımını hesapla
CURRENT_MONTH=$(date '+%Y-%m')
CURRENT_USAGE_GB=0

if [ -f "$USAGE_FILE" ]; then
    CURRENT_USAGE_GB=$(grep "^$CURRENT_MONTH" "$USAGE_FILE" | awk -F',' '{sum += $2} END {print sum/1024/1024/1024}')
fi

# Kota kontrolü
if (( $(echo "$CURRENT_USAGE_GB >= $MONTHLY_LIMIT_GB" | bc -l) )); then
    echo "KOTA ASIMI: $USERNAME kullanicisi $MONTHLY_LIMIT_GB GB limitini asti. Baglanti reddediliyor." 
        >> /var/log/openvpn/quota-violations.log
    exit 1  # Bağlantıyı reddet
fi

echo "Baglanti izni verildi: $USERNAME (Kullanim: ${CURRENT_USAGE_GB}GB / ${MONTHLY_LIMIT_GB}GB)" 
    >> /var/log/openvpn/quota-check.log
exit 0

Kota dizinlerini hazırlayın:

mkdir -p /etc/openvpn/quotas
mkdir -p /var/log/openvpn/usage
mkdir -p /var/log/openvpn/reports

# Örnek kullanıcı kotası (20GB limit)
echo "20" > /etc/openvpn/quotas/ahmet.yilmaz.quota

Grafana ve InfluxDB ile Görsel Dashboard

Sayıları ekrana basmak yetmez, görsel dashboard olmadan uzun vadeli trend analizi yapılamaz. InfluxDB ve Grafana kombinasyonu bu iş için biçilmiş kaftan.

Önce InfluxDB’ye veri gönderen bir script yazalım:

#!/bin/bash
# /usr/local/bin/vpn-metrics-to-influx.sh

INFLUX_URL="http://localhost:8086"
INFLUX_DB="openvpn_metrics"
INFLUX_TOKEN="your-influxdb-token-here"  # InfluxDB v2 için
STATUS_FILE="/var/log/openvpn/openvpn-status.log"
TIMESTAMP=$(date +%s%N)

# InfluxDB v2 kullanıyorsanız:
send_metric() {
    local measurement=$1
    local tags=$2
    local fields=$3

    curl -s -X POST 
        "${INFLUX_URL}/api/v2/write?org=myorg&bucket=${INFLUX_DB}&precision=ns" 
        -H "Authorization: Token ${INFLUX_TOKEN}" 
        -H "Content-Type: text/plain; charset=utf-8" 
        --data-binary "${measurement},${tags} ${fields} ${TIMESTAMP}"
}

# Aktif bağlantı sayısı
ACTIVE_CLIENTS=$(grep -c "^CLIENT_LIST," "$STATUS_FILE" 2>/dev/null || echo "0")
send_metric "vpn_connections" "server=vpn01" "active_clients=${ACTIVE_CLIENTS}i"

# Kullanıcı bazlı metrikler
grep "^CLIENT_LIST," "$STATUS_FILE" | while IFS=',' read -r header cn real_addr virt_addr virt_ipv6 bytes_rx bytes_tx rest; do
    CLEAN_CN=$(echo "$cn" | tr -d ' ' | tr '.' '_')
    send_metric "vpn_user_bandwidth" 
        "server=vpn01,username=${CLEAN_CN}" 
        "bytes_received=${bytes_rx}i,bytes_sent=${bytes_tx}i"
done

echo "[$(date '+%Y-%m-%d %H:%M:%S')] Metrikler InfluxDB'ye gonderildi." >> /var/log/openvpn/metrics.log

Bu scripti cron’a ekleyin:

*/1 * * * * /usr/local/bin/vpn-metrics-to-influx.sh

Grafana tarafında InfluxDB datasource ekledikten sonra şu sorgu ile dashboard oluşturabilirsiniz:

from(bucket: "openvpn_metrics")
  |> range(start: -24h)
  |> filter(fn: (r) => r._measurement == "vpn_user_bandwidth")
  |> filter(fn: (r) => r._field == "bytes_received" or r._field == "bytes_sent")
  |> aggregateWindow(every: 5m, fn: mean)
  |> yield(name: "mean")

Otomatik Uyarı Sistemi

Bant genişliği eşiklerini aştığında otomatik uyarı almak için şu scripti kullanabilirsiniz:

#!/bin/bash
# /usr/local/bin/vpn-alert-check.sh

STATS_JSON="/var/log/openvpn/vpn_stats.json"
ALERT_LOG="/var/log/openvpn/alerts.log"
ADMIN_MAIL="[email protected]"
SLACK_WEBHOOK="https://hooks.slack.com/services/XXXX/YYYY/ZZZZ"

# Eşik değerleri
MAX_SINGLE_USER_MB=1024    # Tek kullanıcı için 1GB/bağlantı uyarısı
MAX_TOTAL_MB=10240         # Toplam 10GB uyarısı
MAX_CONCURRENT_USERS=50    # Maksimum eşzamanlı kullanıcı

send_slack_alert() {
    local message=$1
    curl -s -X POST -H 'Content-type: application/json' 
        --data "{"text":"*VPN UYARISI*: ${message}"}" 
        "$SLACK_WEBHOOK" > /dev/null 2>&1
}

send_email_alert() {
    local subject=$1
    local body=$2
    echo "$body" | mail -s "$subject" "$ADMIN_MAIL" 2>/dev/null
}

if [ ! -f "$STATS_JSON" ]; then
    echo "Stats dosyasi bulunamadi: $STATS_JSON" >&2
    exit 1
fi

# Python ile JSON analizi
python3 << PYEOF
import json, sys, subprocess
from datetime import datetime

with open('$STATS_JSON', 'r') as f:
    data = json.load(f)

alerts = []
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

# Eşzamanlı kullanıcı kontrolü
if data['active_clients'] > $MAX_CONCURRENT_USERS:
    msg = f"Cok fazla esit zamanli kullanici: {data['active_clients']} (Limit: $MAX_CONCURRENT_USERS)"
    alerts.append(msg)

# Toplam bant genişliği kontrolü
if data['total_rx_mb'] > $MAX_TOTAL_MB:
    msg = f"Toplam indirme limiti asildi: {data['total_rx_mb']} MB (Limit: $MAX_TOTAL_MB MB)"
    alerts.append(msg)

# Kullanıcı bazlı kontrol
for client in data['clients']:
    if client['bytes_received_mb'] > $MAX_SINGLE_USER_MB:
        msg = f"Yuksek kullanim: {client['username']} - {client['bytes_received_mb']} MB indirdi (IP: {client['real_address']})"
        alerts.append(msg)

if alerts:
    for alert in alerts:
        print(f"[{timestamp}] UYARI: {alert}")
        with open('$ALERT_LOG', 'a') as log:
            log.write(f"[{timestamp}] {alert}n")
        subprocess.run(['bash', '-c', f'/usr/local/bin/vpn-alert-check.sh slack_notify "{alert}"'])
else:
    print(f"[{timestamp}] Tum kontroller normal.")

sys.exit(1 if alerts else 0)
PYEOF

Logrotate ile Log Yönetimi

Bu kadar log üretince bunları yönetmek de önemli hale geliyor:

# /etc/logrotate.d/openvpn-custom
/var/log/openvpn/bandwidth-history.csv
/var/log/openvpn/vpn_stats.json
/var/log/openvpn/alerts.log
/var/log/openvpn/metrics.log {
    daily
    rotate 90
    compress
    delaycompress
    missingok
    notifempty
    dateext
    dateformat -%Y%m%d
    postrotate
        systemctl reload openvpn@server > /dev/null 2>&1 || true
    endscript
}

Gerçek Dünya Senaryosu: Kurumsal Ortamda Kota Uyarısı

Bir yazılım şirketinde, 30 remote çalışan var ve hepsi ofis VPN’i üzerinden iç sistemlere bağlanıyor. Aylık 1TB bant genişliği limitiniz var ve ay sonuna 5 gün kala 900GB’a ulaştınız. Bu durumda ne yaparsınız?

Önce kim ne kullanmış bakalım:

# Son 30 günün CSV'sinden en çok tüketen kullanıcıları bul
awk -F',' '
NR>1 {
    users[$2] += ($5 + $6)
}
END {
    for (u in users) {
        printf "%.2f GBt%sn", users[u]/1024/1024/1024, u
    }
}
' /var/log/openvpn/bandwidth-history.csv | sort -rn | head -10

Bu komutun çıktısı size doğrudan “şüpheli” kullanıcıları gösterecek. Belki birisi backup scriptini VPN üzerinden çalıştırıyor, belki biri video stream yapıyor. Veriyi gördükten sonra ilgili kişiyle konuşmak ya da split tunneling politikasını gözden geçirmek çok daha kolay.

Split tunneling konfigürasyonunu server.conf üzerinde kontrol altına alarak yalnızca iç ağ trafiğinin VPN’den geçmesini sağlayabilirsiniz:

# server.conf - Sadece iç ağ trafiğini VPN'e yönlendir
push "route 10.0.0.0 255.255.0.0"
push "route 172.16.0.0 255.255.0.0"
# push "redirect-gateway def1" satırını KALDIRIN veya yorum yapın

Bu değişiklik tek başına bant genişliği kullanımını dramatik biçimde düşürebilir.

Sonuç

OpenVPN bant genişliği izleme, “kurar ve unutursun” değil aktif yönetim gerektiren bir konu. Status dosyasıyla başlayıp management interface’e geçmek, oradan InfluxDB ve Grafana ile görselleştirmeye ulaşmak kademeli bir süreç. Her şeyi bir gecede kurmaya çalışmayın.

En az yapmanız gerekenler şunlar: status dosyasını aktifleştirin, 5 dakikada bir snapshot alan scripti cron’a ekleyin, vnstat ile arayüz bazlı günlük takibi başlatın. Bu üç adım bile sizi “kör uçuş” durumundan kurtarır.

Orta vadede management interface üzerinden Python scriptiyle JSON çıktısı almak ve bunu Grafana’ya beslemek en iyi yatırım. Bir kez kurduğunuzda tüm VPN trafiğini cam arkasından izler gibi görmeye başlarsınız. Kapasite planlaması, kullanıcı bazlı sorun tespiti ve güvenlik analizlerini veriye dayalı yapabilmek, sysadmin’i reaktif değil proaktif kılar. Bu fark, sabah 3’te telefon çalıp çalmaması arasındaki farktır.

Yorum yapın