Skip to main content

systemd-service zum Zählen von Zugriffen nach ASN

Ich will es wissen: Woher kommen die Zugriffe auf meinen Rootserver? Und stellt das ein Problem dar?

Die letzte Frage kann ich klar mit "nein" beantworten. Der Server langweilt sich die meiste Zeit. Aber neben meinem Rootserver habe ich noch einen virtuellen Server (VPS, virtual private server), der ein bisschen mehr zu tun hat und bei dem es ein Problem sein könnte, wenn da zu viel Mist aufläuft. Der Rootserver ist aber ein gutes Versuchskaninchen.

Anarcat hat letztes Jahr das Python-Programm asncounter veröffentlicht, das Logs aus verschiedenen Quellen nehmen kann, um sie zu analysieren und eine ASN-Statistik auszugeben. Im (englischsprachigen, aber gut verständlichen) Blogbeitrag befinden sich Erklärungen zu AS, ASN und BGP.

Der Server läuft derzeit noch mit Debian 12. Anarcat stellt in Aussicht, dass asncounter in Debian 14 in den offiziellen Repositorys enthalten seien könnte. Bis dahin ist die beste Installationsmethode wohl mittels pip bzw. pipx. Ich habe mich für letzteres entschieden. Der Einfachheit halber installiere ich gleich die Pakete mit, die bei der Installation von asncounter benötigt werden:

apt install pipx build-essential python3-dev

Ich mag es nicht, wenn pipx die virtuellen Environments und so in /root erstellt. Deswegen setze ich in der /root/.bashrc ein paar Variablen für pipx:

export PIPX_BIN_DIR=/usr/local/bin
export PIPX_HOME=/usr/local/share/pipx
eval "$(register-python-argcomplete pipx)"

Die letzte Zeile dient der Tabvervollständigung von pipx-Befehlen. Einmal kurz ausgeloggt und wieder eingeloggt, geht es weiter:

pipx install --include-deps asncounter

Das installiert den asncounter inklusive seiner Abhängigkeiten. Der Blogbeitrag erwähnt im tl;dr am Anfang eine Befehlszeile folgenden Formats:

tcpdump -q -i enp0s31f6 -n -Q in "tcp and tcp[tcpflags] & tcp-syn != 0 and (port 80 or port 443)" | asncounter --input-format=tcpdump --repl

Ich lese "tcpdump" jetzt nicht so wahnsinnig flüssig, aber ich erkenne eine Filterung nach TCP-Zugriffen auf Ports 80 und 443. Das soll mir für den Anfang reichen. Dummerweise hänge ich dann im REPL fest und muss die dazugehörige man-Page erstmal ein wenig gründlicher lesen, wo ich auf recorder.display_results() stoße:

>>> recorder.display_results()
count       percent ASN     AS
7248        85.27   265269  MEGA TELE INFORMATICA, BR
1132        13.32   270606  R3 TELECOM, BR
25  0.29    54801   ZILLION-NETWORK, US
25  0.29    45102   ALIBABA-CN-NET Alibaba US Technology Co., Ltd., CN
15  0.18    209334  MODAT-01, NL
11  0.13    197540  NETCUP-AS netcup GmbH, DE
6   0.07    16276   OVH, FR
4   0.05    213438  COLOCATEL-INC Colocatel Network - High Bandwidth Dedicated Servers, SC
4   0.05    14061   DIGITALOCEAN-ASN, US
3   0.04    211607  RECORDEDFUTURE, US
unique ASN: 26
count       percent prefix  ASN     AS
1826        21.48   168.90.91.0/24  265269  MEGA TELE INFORMATICA, BR
1823        21.45   168.90.89.0/24  265269  MEGA TELE INFORMATICA, BR
1806        21.25   168.90.88.0/23  265269  MEGA TELE INFORMATICA, BR
1793        21.09   168.90.90.0/24  265269  MEGA TELE INFORMATICA, BR
310 3.65    177.37.19.0/24  270606  R3 TELECOM, BR
282 3.32    177.37.17.0/24  270606  R3 TELECOM, BR
273 3.21    177.37.18.0/24  270606  R3 TELECOM, BR
267 3.14    177.37.16.0/24  270606  R3 TELECOM, BR
25  0.29    148.178.64.0/20 54801   ZILLION-NETWORK, US
25  0.29    47.83.0.0/17    45102   ALIBABA-CN-NET Alibaba US Technology Co., Ltd., CN
unique prefixes: 34
total lookups: 8500
total skipped: 0
total failed: 0

asncounter gibt also im Falle des Durchpipens von tcpdump nur auf explizite Anfrage Werte aus. Damit kann man sicher arbeiten, aber mir wird beim Gedanken, irgendwelche Sockets regelmäßig abzufragen, ein wenig flau im Magen. Langlaufende Prozesse, die ich verbrochen habe? Nein, danke :-).

Aber tcpdump minütlich Dinge erfassen lassen, die ich asncounter dann zum Analysieren vorwerfen kann? Klar, das sicher. Und so lernte ich heute von timeout, das genau das tut, was ich will:

timeout -sHUP 1m tcpdump -q -i enp0s31f6 -n -Q in "tcp and tcp[tcpflags] & tcp-syn != 0 and (port 80 or port 443)" > tcpdump4asncounter.log

Jetzt brauche ich nur ein Bash-Skript, das mir jede Minute eine Datei mit diesen Daten erstellt.

#!/usr/bin/env bash
CACHE_DIR=/var/cache/count_asn
IF="${INTERFACE:-any}"
DIR="${CACHE_DIRECTORY:-${CACHE_DIR}}"
printf "Interface set to %s.\n" "${IF}"
printf "Cache directory set to %s.\n" "${DIR}"

cleanup () {
    #kill -s SIGTERM $!
    exit 0
}

trap cleanup SIGINT SIGTERM

if [ ! -d "${DIR}" ]
then
    install -d "${DIR}"
fi

printf "Starting logging loop …\n"
while true
do
    CURRENT=$(date +%s)
    timeout -sHUP 10s tcpdump -q -i "${IF}" -n -Q in "tcp and tcp[tcpflags] & tcp-syn != 0 and (port 80 or port 443)" > "/tmp/count_asn_${CURRENT}.log" 2> /dev/null
    find "${DIR}" -type f -mtime +1 -delete
    mv "/tmp/count_asn_${CURRENT}.log" "${DIR}/"
    cat "${DIR}/"count_asn_*.log | /usr/local/bin/asncounter --input-format tcpdump --no-prefixes --output /etc/motd.d/01-asncounter.txt 2> /dev/null
done

Am Anfang werden ein paar Variablen initialisiert, die ich entweder per systemd übergebe oder eben auch nicht. Dann braucht es aber sinnvolle Defaults.

Ich habe feststellen dürfen, dass sich so eine while-true-Schleife nicht mit Strg+c abbrechen lässt. Vielleicht liegt es auch an timeout. Deswegen werden die Signale SIGINT und SIGTERM explizit abgefangen und in der cleanup()-Funktion behandelt. Das aktuelle tcpdump läuft aber noch bis zum Ende durch. Damit kann ich leben.

Der Dump wird erstmal nur nach /tmp geschrieben. Dann werden die Dateien entfernt, die älter als 1 Tag sind, bevor die neue Datei in den Cache geschoben wird. Der Cache-Inhalt wird dann asncounter zur Analyse übergeben, das Ergebnis wird nach /etc/motd.d geschrieben (Verzeichnis muss manuell erstellt werden). Und auf diese Weise lernte ich, dass pam_login auch die Dateien dieses Verzeichnisses ausgibt statt nur /etc/motd.

Natürlich muss das Skript ausführbar gemacht werden:

chmod +x /usr/local/bin/count_asn

Jetzt muss das Skript nur noch dauerhaft laufen. Ich benutze dafür gerne systemd. Schnell mal eine Vorlage im Web herausgesucht und an meine Bedürfnisse angepasst. Ich bin leider immer noch nicht so weit, dass ich das aus dem Ärmel schütteln kann:

# /etc/systemd/system/count_asn.service
[Unit]
Description=Count ASNs using asncounter and update motd
After=network.target

[Service]
Type=simple
Environment=INTERFACE=enp0s31f6
ExecStart=/usr/local/bin/count_asn
Restart=always
RestartSec=5
CacheDirectory=count_asn
IgnoreSIGPIPE=no

[Install]
WantedBy=multi-user.target

Der Install-Abschnitt gibt wie immer an, an welcher Stelle beim Booten der Dienst gestartet werden soll, wenn er enabled wird. Im Unit-Abschnitt sagen wir an, dass der Dienst erst nach dem Erreichen des network.target gestartet werden soll. Verfügbarkeit von Netzwerk ist eine Wissenschaft für sich, aber hier sollte das passen.

Der wichtigste Teil ist hier IgnoreSIGPIPE=no, ansonsten klappt das Pipen der Daten an asncounter nicht. Das Skript kriegt mit der Umgebungsvariable INTERFACE noch die richtige Schnittstelle mitgeteilt.

Lohn der Mühe:

$ ssh root@hades
Linux hades.fluchtkapsel.de 6.1.0-41-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.158-1 (2025-11-09) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
count       percent ASN     AS
21811       78.65   265269  MEGA TELE INFORMATICA, BR
5486        19.78   265410  JL INFORMATICA E TELECOM LTDA - ME, BR
108 0.39    14618   AMAZON-AES, US
71  0.26    197540  NETCUP-AS netcup GmbH, DE
43  0.16    8075    MICROSOFT-CORP-MSN-AS-BLOCK, US
40  0.14    202306  HOSTGLOBALPLUS-AS, GB
31  0.11    51852   PLI-AS, PA
17  0.06    206264  AMARUTU-TECHNOLOGY, SC
14  0.05    398705  CENSYS-ARIN-02, US
10  0.04    62068   SPECTRAIP SpectraIP B.V., NL
unique ASN: 40
total lookups: 27730
total skipped: 152
total failed: 0
Last login: Fri Jan  9 18:05:35 2026 from 79.248.89.181

Ich werde beim Login mit der Statistik begrüßt. Das sieht mir jetzt robust genug aus, dass ich das auch auf meinem VPS einrichten kann. Dort läuft Arch Linux. Als erstes installiere ich pipx:

pacman -Sy python-pipx python-pip base-devel

python-pip ist nur dafür dabei, falls ich mal manuell in die venv schauen muss, und base-devel ist das Arch-Äquivalent zu Debians build-essential. Als nächstes kommen die Einträge in die .bashrc von oben gefolgt von einem Logout und erneuten Login.

Ich übertrage das Skript und den Service, passe die Schnittstelle an, erstelle das Verzeichnis /etc/motd.d und starte den Service. Nach ein paar Minuten gibt es dann auch hier Statistiken:

$ ssh root@persephone
count       percent ASN     AS
651 58.97   265269  MEGA TELE INFORMATICA, BR
151 13.68   265410  JL INFORMATICA E TELECOM LTDA - ME, BR
113 10.24   24940   HETZNER-AS, DE
61  5.53    14618   AMAZON-AES, US
22  1.99    16276   OVH, FR
12  1.09    197540  NETCUP-AS netcup GmbH, DE
8   0.72    3320    DTAG Internet service provider operations, DE
8   0.72    63949   AKAMAI-LINODE-AP Akamai Connected Cloud, SG
8   0.72    15169   GOOGLE, US
7   0.63    14061   DIGITALOCEAN-ASN, US
unique ASN: 52
total lookups: 1104
total skipped: 7
total failed: 9462

Deutlich weniger Betrieb. Das überrascht. Im nächsten Schritt muss ich dann mal schauen, ob ich aus diesen Daten sinnvolle Regeln für die Firewall ableiten kann. Auf beiden Systemen läuft nftables – das macht es mir ein wenig einfacher. Aber das ist eine Aufgabe für einen Folge-Eintrag ins Blog.

Dieser Blog-Eintrag wurde ohne Unterstützung generativer KI erstellt.

Comments

With an account on the Fediverse or Mastodon, you can respond to this post. Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one. Known non-private replies are displayed below.