Ich habe mein Netzwerk schon seit Jahrzehnten in Betrieb und entsprechend bin ich auch etwas nerdig, was mein Equipment betrifft.
Ich habe z.B. keine FRITZ!Box und auch sonst keinen "gewöhnlichen" Router, stattdessen erledigt hier OPNsense die Routingaufgaben, und zwar auf einer APU3D4, sparsam und komplett ohne Videoausgang
Als VDSL Modem setze ich dabei ein DrayTek Vigor 165 ein, eines der wenigen Modelle am Markt, die direkt als "echtes" Modem verwendet werden können, d.h. die OPNsense nutzt PPPoE, um die Verbindung ins Internet aufzubauen.
Nun möchte man ja zumindest ein paar Informationen über den Zustand des Modems haben, denn trotz Großstadt mit Telekom-Vergangenheit kommt es auch hier immer wieder mal zu Ausfällen auf VDSL-Seite.
Mein Ansatz war in diesem Fall auch eher unkonventionell, ich wollte nicht über das Webinterface zugreifen.
Stattdessen habe ich im Modem einen Syslog Server eingetragen, den ich über OPNsense im Verwaltungsnetz verfügbar gemacht habe. (Ja, das Modem kann das von Haus aus...)
Der Syslog Dienst läuft gewöhnlich auf Port 514 mit UDP, es reicht dazu, in der rsyslog.conf des Hosts das Modul imudp zu aktivieren.
Die Standardkonfiguration schreibt für jedes externe System die Logs in ein eigenes Verzeichnis, pro Programm eine eigene Datei, perfekt...
Nun schreibt das Vigor Modem also brav etwa einmal pro Minute eine Logzeile, welche in einer Datei /var/log/meinModem/DrayTek.log landet (wobei meinModem dank unbound dem fqdn des Modems entspricht, der spielt hier aber keine Rolle )
Das Doofe: Der Inhalt der Datei sieht etwa so aus:
Code: Alles auswählen
Jul 15 08:20:59 meinModem DrayTek: ADSL_Status:[Mode=17A States=SHOWTIME UpSpeed=31999000 DownSpeed=69998000 SNR=17 Atten=14 ]
Jul 15 08:21:34 meinModem DrayTek: 9:13:44 [DSL] Status was switched: Showtime(7) to Exception(8)#015
Jul 15 08:21:36 meinModem DrayTek: 9:13:46 [DSL] Status was switched: Exception(8) to Restart(10)#015
Jul 15 08:21:38 meinModem DrayTek: 9:13:48 [DSL] Status was switched: Restart(10) to FirmwareRequest(1)#015
Jul 15 08:21:46 meinModem DrayTek: 9:13:56 [DSL] Entering VDSL2 mode#015
Jul 15 08:21:47 meinModem DrayTek: 9:13:57 [DSL] modem code: [08-0B-02-06-00-07]#015
Jul 15 08:21:47 meinModem DrayTek: 9:13:57 [DSL] Status was switched: FirmwareRequest(1) to firmwareReady(3)#015
Jul 15 08:21:48 meinModem DrayTek: 9:13:58 [DSL] Status was switched: firmwareReady(3) to Init(5)#015
Jul 15 08:21:51 meinModem DrayTek: 9:14:01 [DSL] Status was switched: Init(5) to Train(6)#015
Jul 15 08:23:18 meinModem DrayTek: 9:15:28 [DSL] Status was switched: Train(6) to Showtime(7)#015
Jul 15 08:23:23 meinModem DrayTek: ADSL_Status:[Mode=----- States=SHOWTIME UpSpeed=31999000 DownSpeed=69998000 SNR=15 Atten=14 ]
Jul 15 08:23:23 meinModem DrayTek: 9:15:34 [DSL] VDSL2: UP rate=0, DOWN rate=69992 Kbps, Mode=17A#015
Jul 15 08:23:24 meinModem DrayTek: [DSL] G.Vectoring Status: ON (Bidirection)
Jul 15 08:24:36 meinModem DrayTek: ADSL_Status:[Mode=17A States=SHOWTIME UpSpeed=31999000 DownSpeed=69998000 SNR=17 Atten=14 ]
Jul 15 08:25:47 meinModem DrayTek: ADSL_Status:[Mode=17A States=SHOWTIME UpSpeed=31999000 DownSpeed=69998000 SNR=17 Atten=14 ]
Jul 15 08:26:59 meinModem DrayTek: ADSL_Status:[Mode=17A States=SHOWTIME UpSpeed=31999000 DownSpeed=69998000 SNR=17 Atten=14 ]
Meine "faule" Lösung mit bullseye war, über crontab diese Zeile auszuführen:
Code: Alles auswählen
@reboot /usr/bin/tail -f /var/log/meinModem/DrayTek.log | /usr/bin/mosquitto_pub -h 192.168.178.55 -t draytek/log -l
Das muss besser gehen, aber... keine Zeit...
Bis nun mit debian bookworm die aktuelle Version von cron die Funktion @reboot verlernt hat.
Und was ich auch angestellt habe, es lief darauf hinaus, bei jedem Systemstart daran denken zu müssen, das tail-Kommando manuell zu starten. Nö.
Also muss doch eine Kanone herangerollt werden, mit der ich auf das Problem schießen kann
Da ich schon auf einigen Systemen einen kleinen Python mqtt Client programmiert habe, war es naheliegend, auch hier einen spezialisierten Client zu schreiben.
Code: Alles auswählen
#!/usr/bin/python
import configparser
import logging
from paho.mqtt import client as mqtt
import time
import json
from typing import Iterator
config = configparser.ConfigParser()
config.read('/etc/default/mqttclient')
log_file = (config['logging']).get('filename','/var/log/pymqtt.log')
log_level = int((config['logging']).get('level','30'))
logging.basicConfig(filename=log_file,format='%(asctime)s %(levelname)s:%(message)s', datefmt='%Y/%m/%d/ %H:%M:%S', level=log_level)
MQTT_SERVER = (config['mqtt']).get('url','localhost')
MQTT_PORT = int((config['mqtt']).get('port','1883'))
MQTT_RECONNECT = int((config['mqtt']).get('reconnect','60'))
MQTT_PATH = (config['mqtt']).get('path','draytek/')
FILEPATH = (config['file']).get('path','/var/log/test.log')
# Sobald die Verbindung zum Broker steht
def on_connect(client, userdata, flags, rc):
client.publish(MQTT_PATH+"LWT", payload="Online", qos=0, retain=True)
logging.info("Connected with result code "+str(rc))
if rc: logging.warning("Connecting resulted with" +str(rc))
# Routine zum Lesen und zeilenweisen Ausgeben einer Datei
def follow(file, sleep_sec=0.1) -> Iterator[str]:
""" Yield each line from a file as they are written.
`sleep_sec` is the time to sleep after empty reads. """
line = ''
while True:
tmp = file.readline()
if tmp is not None:
line += tmp
if line.endswith("\n"):
yield line.rstrip()
line = ''
elif sleep_sec:
time.sleep(sleep_sec)
# Los geht's
client = mqtt.Client("draytek")
client.on_connect = on_connect
client.will_set(MQTT_PATH+"LWT", payload="Offline", qos=0, retain=True)
client.connect(MQTT_SERVER, MQTT_PORT, MQTT_RECONNECT)
client.loop_start()
with open(FILEPATH, 'r') as file:
for line in follow(file):
thelog = ''
# falls die Zeichenfolge 'DrayTek: ' vorkommt, alles dahinter übernehmen
if line.find('DrayTek: ') > 1:
thelog = line.split('DrayTek: ')[1]
logging.debug(thelog)
# falls die Zeichenfolge 'ADSL_Status:' vorkommt, Zeile für das Topic adsl aufbereiten
if thelog.find('ADSL_Status:') > -1:
payload = {}
payload['mode'] = line.split('Mode=')[1].split(' ')[0]
payload['state'] = line.split('States=')[1].split(' ')[0]
payload['upSpeed'] = int(line.split('UpSpeed=')[1].split(' ')[0])
payload['downSpeed'] = int(line.split('DownSpeed=')[1].split(' ')[0])
payload['snr'] = int(line.split('SNR=')[1].split(' ')[0])
payload['atten'] = int(line.split('Atten=')[1].split(' ')[0])
payload['timestamp'] = time.time()
payload=json.dumps(payload)
logging.info(payload)
client.publish(MQTT_PATH+"adsl", payload, qos=0, retain=False)
# falls die Zeichenfolge '[DSL]' vorkommt, Zeile für das Topic dsl aufbereiten
if thelog.find('[DSL]') > -1:
payload = {}
payload['DSL'] = line.split('[DSL] ')[1]
payload['timestamp'] = time.time()
payload=json.dumps(payload)
logging.info(payload)
client.publish(MQTT_PATH+"dsl", payload, qos=0, retain=False)
# wenn im DSL-Block die Zeichenfolge 'ON (Bidi' auftaucht, das Topic state auf ONLINE setzen, ansonsten auf OFFLINE
if thelog.find('ON (Bidi'):
client.publish(MQTT_PATH+"state", "ONLINE", qos=0, retain=False)
else:
client.publish(MQTT_PATH+"state", "OFFLINE", qos=0, retain=False)
Code: Alles auswählen
draytek/dsl {"G.Vectoring Status: ON (Bidirection)", "timestamp": 1689444854.9230878}
draytek/adsl {"mode": "17A", "state": "SHOWTIME", "upSpeed": 31999000, "downSpeed": 69998000, "snr": 18, "atten": 14, "timestamp": 1689444854.9233086}
draytek/state ONLINE
Code: Alles auswählen
[mqtt]
url = 192.168.178.33
port = 1883
reconnect = 60
path = draytek/
lwt = LWT
[file]
path = /var/log/meinModem/DrayTek.log
[logging]
filename = /var/log/pymqtt.log
# CRITICAL=50 ERROR=40 WARNING=30 INFO=20 DEBUG=10 NOTSET=0
level = 30
Und natürlich darf eine service-Datei zum Start als Dienst nicht fehlen:
Code: Alles auswählen
[Install]
WantedBy=multi-user.target
[Service]
ExecStart=/usr/bin/python3 /usr/local/bin/mqttclient.py
User=mqtt
Restart=always
RestartSec=60
Code: Alles auswählen
Thing topic draytek "Vigor 165" @ "mqtt" {
Type string : adsl "ADSL" [ stateTopic="draytek/adsl" ]
Type string : dsl "DSL" [ stateTopic="draytek/dsl" ]
Type switch : state "Status" [ stateTopic="draytek/state", on="ONLINE", off="OFFLINE" ]
}
Code: Alles auswählen
String DraytekMode "Mode" <network> (gModem) ["Status"] {channel="mqtt:topic:mymqtt:draytek:adsl"[profile="transform:JSONPATH",function="$.mode"]}
Number DraytekUp "Upload" <network> (gModem) ["Status"] {channel="mqtt:topic:mymqtt:draytek:adsl"[profile="transform:JSONPATH",function="$.upSpeed"]}
Number DraytekDown "Download" <network> (gModem) ["Status"] {channel="mqtt:topic:mymqtt:draytek:adsl"[profile="transform:JSONPATH",function="$.downSpeed"]}
Number DraytekSNR "SNR" <network> (gModem) ["Status"] {channel="mqtt:topic:mymqtt:draytek:adsl"[profile="transform:JSONPATH",function="$.snr"]}
Number DraytekAtten "Atten" <network> (gModem) ["Status"] {channel="mqtt:topic:mymqtt:draytek:adsl"[profile="transform:JSONPATH",function="$.atten"]}
Switch DraytekOnline "Online" <network> (gModem) ["Status"] {channel="mqtt:topic:mymqtt:draytek:state"}
Number DraytekTime "Zeit" <network> (gModem) ["Status"] {channel="mqtt:topic:mymqtt:draytek:adsl"[profile="transform:JSONPATH",function="$.timestamp"]}
Vielleicht baue ich noch weitere Parseroptionen in den Client, um die DSL-Zeilen weiter zu zerlegen, aber für's erste spielt es jetzt so, wie ich es mir wünsche das dsl-Topic nutze ich jetzt erst mal nicht aktiv, online/offline reicht mir hier.