- Mitglied seit
- 10 Mai 2006
- Beiträge
- 15,275
- Punkte für Reaktionen
- 1,751
- Punkte
- 113
Da ich ab und an mal Bezug darauf nehme und dann immer auf ein Freetz-Ticket verweisen muß, will ich mal etwas zum Lua-Variableninterface schreiben und wie man von der Shell aus darauf zugreifen kann. Sollte ich einen anderen Beitrag dazu übersehen haben, ist entweder der Vortrag des Redners Glück oder man kann vergleichen. Wenn es am Ende zwei nahezu identische Beiträge wären, lösche ich meinen aber auch wieder ... aber ich habe bisher nichts dazu gefunden.
Man kann das Lua-Variableninterface mit dem Utility /bin/luavar benutzen, das irgendwann mal von AVM in die Firmware integriert wurde. Die Verwendung dieses Programms durch AVM selbst habe ich bisher nur bei den DOCSIS-Modellen gesehen, wo damit in einigen Fällen Daten zu den SIP-Telefonnummern vom SNMP-Stack in die "normale" voip.cfg übertragen werden.
So wie es von AVM auch in den Lua-Files für das GUI praktiziert wird, kann man über das Interface auch komplette "Listen" von Einträgen abfragen, sogar mit der Vorgabe der "Grenzen" für eine Art Paging.
Für die reine Abfrage von Werten kann man (als funktionsfähiges Beispiel) das folgende Script verwenden:
EDIT: Das Script wurde noch etwas geändert, um einerseits einen ordentlichen Exit-Code zu setzen und andererseits Fehlernachrichten nicht mehr nach 'stdout', sondern wie es sich gehört nach 'stderr' auszugeben.
Abspeichern (z.B. als "queries.lua") und "ausführbar" machen ... es ist kein Shell-Script, somit macht ein Aufruf mit "sh" auch keinen Sinn. Wenn schon, dann müßte es eine Zeile "luavar queries.lua" sein ... dank "shebang" in der ersten Zeile muß man sich bei einer ausführbaren Datei darum aber nicht kümmern.
Über STDIN erwartet das Script Zeilen in der Form
Solange dabei query eine einfache Abfrage eines Wertes ist, wird dieser als eine Zeile
ausgegeben, wenn die Abfrage keiner bekannten Variablen entspricht, wird als Wert "***no result***" verwendet.
Spannender wird es dann, wenn es um die Abfrage einer Liste geht. Diese hat ja in der AVM-Firmware z.B. das Format
und in der Eingabedatei wird daraus dann eine Zeile:
Nach der Abfrage der Variablen wird dann eine Liste in der folgenden Form ausgegeben:
Für jeden Eintrag in der Liste wird also anhand seiner Position ein Index n gebildet und die einzelnen Felder der Abfrage werden dann als Zuweisungen zu einer passenden Variablen ausgegeben. Der spezielle "Feldname" index wird für die Ausgabe des Wertes benutzt, den man benötigt, um für diesen einen Eintrag eine gezielte Abfrage auszuführen. Wenn also die Abfrage von
ein Ergebnis der Form
liefert, dann kann man für die zweite Nummer (sip1, in der voip.cfg als ua2 abgelegt) weitere Angaben gezielt mit einer Zeile
ermitteln.
Zurück zur Liste ... der zusätzliche Eintrag "variable_count" in der Ausgabe einer Liste (er wird immer am Ende der Liste angefügt, auch wenn er oben als erster Eintrag aufgeführt ist) enthält die Anzahl der Zeilen in der "Ergebnistabelle". Wenn man also mit einem weiteren Script über die Ausgabe iterieren will, hat man mit dieser Zeile den maximalen Wert seines Schleifenzählers.
Beispiele für abfragbare Daten findet man in der AVM-Firmware (insb. in den Lua-Files für das GUI) in Massen ... es gibt aber m.W. nur eine einzige Stelle bisher, wo von den "Paging"-Fähigkeiten der Schnittstelle auch Gebrauch gemacht wird.
Bei einer Abfrage der Form
wird die Menge der ausgegebenen Daten bereits von der "box.multiquery"-Funktion begrenzt. Es werden nur noch y Einträge ausgegeben, beginnend mit dem x-ten Eintrag (nullbasiert).
Zum Abschluß noch ein komplettes Beispiel für mögliche Abfragen, allerdings aus vollkommen verschiedenen Firmware-"Bereichen" und somit bitte wirklich nur als Beispiel zu verstehen. Eine Eingabedatei der Form
- als "abfragen" gespeichert - wird angenommen. Dann sähe ein Aufruf z.B. so aus (gesetzt den Fall, der Dateiname von oben wurde verwendet und das Script unter /var/media/ftp/queries.lua abgelegt):
und ein Ergebnis könnte so aussehen (auch wenn ich von einem Portforwarding für einen SSH-Server auf Port 22 dringend abraten würde):
Die letzten Zeilen oben zeigen zwei unterschiedliche Ausgaben für ungültige Variablennamen.
Wenn die AVM-Firmware für einen Wert "nil" zurückgibt (also "nichts"), dann wird vom Script "***no result***" eingesetzt.
Bei bestimmten "Sektionen" kann es aber schon passieren, daß von der Firmware für ungültige Namen der Wert "non-emu" zurückgegeben wird, wie man es von ctlmgr_ctl auch kennt.
Und noch ein weiterer Punkt wird oben deutlich: Für die eindeutige Benennung der Variablen bei "Einzelabruf" (oben in den Zeilen mit "SIP1=..." gezeigt) ist der Aufrufer selbst verantwortlich. Wenn da doppelte Namen verwendet werden, juckt das das Script in keinster Weise, da die Eingabedatei Zeile für Zeile abgearbeitet wird. Die Probleme kommen dann u.U. erst beim Parsen der Ergebnisse ...
Wie man das dann ziemlich leicht wieder in Shell-Variable umwandelt (eval) und es im weiteren Verlauf dann auseinandernimmt und passend wieder zusammensetzt, überlasse ich der Phantasie des Lesers. Ich wollte nur zeigen, daß es jenseits des "üblichen" ctlmgr_ctl noch andere Wege gibt, an AVM-Firmwareeinstellungen zu gelangen und dabei unter anderem auch auf Werte zuzugreifen, die ansonsten über das ctlmgr-Interface nicht ohne weiteres abzufragen sind.
Man kann über das Lua-Interface auch Variablen setzen, dazu komme ich gleich. Der weitaus häufigere Fall dürfte die Abfrage von Einstellungen sein, vielleicht hilft es ja jemandem weiter. Einiges von den Einstellungen läßt sich auch Stück für Stück über einzelne Abfragen per ctlmgr_ctl ermitteln, bei mir macht sich aber z.B. der Unterschied zwischen der einzelnen Abfrage der Einstellungen für >20 VPN-Verbindungen (jeweils 10 Einstellungen) und der "kompakten Listenabfrage" (so auch im AVM-GUI zu finden)
schon bemerkbar. Anstelle von 200 Aufrufen für ctlmgr_ctl bleibt eben nur noch ein einzelner Scriptaufruf übrig, das Parsen der Ergebnisse sieht am Ende ziemlich identisch aus in beiden Varianten.
Zum Setzen von Einstellungen kann man das folgende Script verwenden:
Bei mir heißt dieses Script ab jetzt "set.lua". Die Anwendung erfolgt analog zu "queries.lua", jedoch werden Zeilen der Form
erwartet. Es können also auch mehrere Einstellungen auf einmal gesetzt werden, bei einigen Firmware-Einstellungen ist das sogar erforderlich, da ansonsten ein Fehler auftritt, wenn am Ende ein "Set" aus Einstellungen (z.B. für einen DynDNS-Account) unvollständig ist. Auch hier kann wieder ein Blick in die Lua-Dateien des AVM-GUI die richtigen Variablennamen offenbaren.
Sollte beim Setzen von Einstellungen ein Fehler auftreten und eine Fehlermeldung dazu vom Variableninterface zurückgegeben werden, wird diese auf stderr ausgegeben und ein Exit-Code von 1 gesetzt. Ob und wie weit dann Zuweisungen teilweise ausgeführt wurden, hängt nach meinen Tests von der Reihenfolge der Anweisungen ab ... ich würde die Vermutung wagen, daß beim ersten Fehler abgebrochen wird.
Auch hier mal ein Beispiel, wir setzen eine neue statische IPv4-Route:
Wer lieber ein Script-File hätte, bei dem man für das Setzen einzelner Einstellungen diese direkt als Parameter angeben kann und keine "Eingabedatei" für stdin benötigt, der nimmt dann dieses Wrapper-File als "setvar.sh":
Es ist mir jedenfalls nicht gelungen, über das AVM-Utility "/bin/luavar" irgendwie an die auf der Kommandozeile angegebenen Parameter zu gelangen.
Man kann das Lua-Variableninterface mit dem Utility /bin/luavar benutzen, das irgendwann mal von AVM in die Firmware integriert wurde. Die Verwendung dieses Programms durch AVM selbst habe ich bisher nur bei den DOCSIS-Modellen gesehen, wo damit in einigen Fällen Daten zu den SIP-Telefonnummern vom SNMP-Stack in die "normale" voip.cfg übertragen werden.
So wie es von AVM auch in den Lua-Files für das GUI praktiziert wird, kann man über das Interface auch komplette "Listen" von Einträgen abfragen, sogar mit der Vorgabe der "Grenzen" für eine Art Paging.
Für die reine Abfrage von Werten kann man (als funktionsfähiges Beispiel) das folgende Script verwenden:
EDIT: Das Script wurde noch etwas geändert, um einerseits einen ordentlichen Exit-Code zu setzen und andererseits Fehlernachrichten nicht mehr nach 'stdout', sondern wie es sich gehört nach 'stderr' auszugeben.
Rich (BBCode):
#! /bin/luavar
lineno = 0;
rc = 0;
while true do
local line = io.read("*line");
if (line == nil) then
if (lineno == 0) then
io.stderr:write("Missing requests\n");
os.exit(2);
end
break;
end
lineno = lineno + 1;
local varname, query = string.match(line, "(.*)=(.*)");
if (varname ~= nil and query ~= nil) then
if (string.find(query, "%(.*%)") == nil) then
local single_res = box.query(query);
if (single_res == nil) then
print(varname.."=\"***no result***\"");
else
print(varname.."=\""..single_res.."\"");
end
else
local columns = {};
local first, last = string.find(query, "%(.*%)");
local list = string.sub(query, first + 1, last - 1);
for col in string.gmatch(list, "[^,]+") do
table.insert(columns, col);
end
if (string.find(query, "listwindow%(.*%)") ~= nil) then
table.remove(columns, 1);
table.remove(columns, 1);
end
local result = box.multiquery(query);
local index = 0;
for i, entry in ipairs(result) do
for j, value in ipairs(entry) do
j = j - 1;
if (j > 0) then
colname = columns[j];
else
colname = "index";
end
print(varname.."_"..i.."_"..colname.."=\""..value.."\"");
end
index = i;
end
print(varname.."_count="..index);
end
else
io.stderr:write("Malformed request at line ",lineno,"\n");
rc = 1;
-- die folgende Zeile aktivieren (-- am Beginn entfernen), um bei falscher Eingabe die Verarbeitung abzubrechen
-- break;
end
end
os.exit(rc);
Über STDIN erwartet das Script Zeilen in der Form
Rich (BBCode):
variable=query
Rich (BBCode):
variable="value"
Spannender wird es dann, wenn es um die Abfrage einer Liste geht. Diese hat ja in der AVM-Firmware z.B. das Format
Rich (BBCode):
sip:settings/sip/list(ID,displayname)
Rich (BBCode):
SIP=sip:settings/sip/list(ID,displayname)
Rich (BBCode):
SIP_count=m
SIP_n_index=single_name
SIP_n_ID=value
SIP_n_displayname=value
Rich (BBCode):
SIP=sip:settings/sip/list(ID,displayname)
Rich (BBCode):
SIP_1_index="sip0"
SIP_1_ID="0"
SIP_1_displayname="80"
SIP_2_index="sip1"
SIP_2_ID="1"
SIP_2_displayname="82"
SIP_3_index="sip2"
SIP_3_ID="18"
SIP_3_displayname="620"
SIP_count=3
Rich (BBCode):
REGSRVR=sip:settings/sip1/registrar
Zurück zur Liste ... der zusätzliche Eintrag "variable_count" in der Ausgabe einer Liste (er wird immer am Ende der Liste angefügt, auch wenn er oben als erster Eintrag aufgeführt ist) enthält die Anzahl der Zeilen in der "Ergebnistabelle". Wenn man also mit einem weiteren Script über die Ausgabe iterieren will, hat man mit dieser Zeile den maximalen Wert seines Schleifenzählers.
Beispiele für abfragbare Daten findet man in der AVM-Firmware (insb. in den Lua-Files für das GUI) in Massen ... es gibt aber m.W. nur eine einzige Stelle bisher, wo von den "Paging"-Fähigkeiten der Schnittstelle auch Gebrauch gemacht wird.
Bei einer Abfrage der Form
Rich (BBCode):
variable=section:settings/listwindow(x,y,field_1,...,field_n)
Zum Abschluß noch ein komplettes Beispiel für mögliche Abfragen, allerdings aus vollkommen verschiedenen Firmware-"Bereichen" und somit bitte wirklich nur als Beispiel zu verstehen. Eine Eingabedatei der Form
Rich (BBCode):
HOST=box:settings/hostname
SSID1=wlan:settings/ssid
SSID2=wlan:settings/ssid_scnd
FORWARDS=forwardrules:settings/rule/list(activated,description,protocol,port,fwip,fwport,endport)
SIPS=sip:settings/sip/list(ID,displayname)
NUMBERS=telcfg:settings/VoipExtension/listwindow(2,2,Name,enabled) <=== eingeschränkte Ergebnismenge
DEVICES=ctlusb:settings/device/count
PHYS=usbdevices:settings/physmedium/list(name,vendor,serial,fw_version,conntype,capacity,status,usbspeed,model)
PHYSCNT=usbdevices:settings/physmediumcnt
VOLS=usbdevices:settings/logvol/list(name,status,enable,phyref,filesystem,capacity,usedspace,readonly)
VOLSCNT=usbdevices:settings/logvolcnt
PARTS=ctlusb:settings/storage-part/count
SIP1=sip:settings/sip1/active <=== ungültiger "Feldname"
SIP1=sip:settings/sip1/activated <=== gültiger Name für einzelnen Wert
HOSTNAME=box:settings/Hostname <=== ungültiger Variablenname
Rich (BBCode):
cat abfragen | /var/media/ftp/queries.lua
Rich (BBCode):
HOST="FB7490"
SSID1="WLAN24"
SSID2="WLAN5"
FORWARDS_1_index="rule0"
FORWARDS_1_activated="1"
FORWARDS_1_description="SSH-Server"
FORWARDS_1_protocol="TCP"
FORWARDS_1_port="22"
FORWARDS_1_fwip="192.168.178.2"
FORWARDS_1_fwport="22"
FORWARDS_1_endport="22"
FORWARDS_count=1
SIPS_1_index="sip0"
SIPS_1_ID="0"
SIPS_1_displayname="80"
SIPS_2_index="sip1"
SIPS_2_ID="1"
SIPS_2_displayname="82"
SIPS_3_index="sip2"
SIPS_3_ID="18"
SIPS_3_displayname="620"
SIPS_count=3
NUMBERS_1_index="VoipExtension2"
NUMBERS_1_Name="3CXPhone"
NUMBERS_1_enabled="1"
NUMBERS_2_index="VoipExtension3"
NUMBERS_2_Name=""
NUMBERS_2_enabled="0"
NUMBERS_count=2
DEVICES="1"
PHYS_1_index="physmedium0"
PHYS_1_name="sys"
PHYS_1_vendor="WDC WD16"
PHYS_1_serial="0"
PHYS_1_fw_version="01.0"
PHYS_1_conntype="USB 2.0"
PHYS_1_capacity="155414663168"
PHYS_1_status="Online"
PHYS_1_usbspeed="480"
PHYS_1_model="00BEVE-11UYT0"
PHYS_count=1
PHYSCNT="1"
VOLS_1_index="logvol0"
VOLS_1_name="data"
VOLS_1_status="Online"
VOLS_1_enable="1"
VOLS_1_phyref="1"
VOLS_1_filesystem="ext3"
VOLS_1_capacity="121594040320"
VOLS_1_usedspace="19144179712"
VOLS_1_readonly="0"
VOLS_2_index="logvol1"
VOLS_2_name="system"
VOLS_2_status="Online"
VOLS_2_enable="1"
VOLS_2_phyref="1"
VOLS_2_filesystem="ext3"
VOLS_2_capacity="33820622848"
VOLS_2_usedspace="990674944"
VOLS_2_readonly="0"
VOLS_count=2
VOLSCNT="2"
PARTS="2"
SIP1="***no result***"
SIP1="1"
HOSTNAME="non-emu"
Wenn die AVM-Firmware für einen Wert "nil" zurückgibt (also "nichts"), dann wird vom Script "***no result***" eingesetzt.
Bei bestimmten "Sektionen" kann es aber schon passieren, daß von der Firmware für ungültige Namen der Wert "non-emu" zurückgegeben wird, wie man es von ctlmgr_ctl auch kennt.
Und noch ein weiterer Punkt wird oben deutlich: Für die eindeutige Benennung der Variablen bei "Einzelabruf" (oben in den Zeilen mit "SIP1=..." gezeigt) ist der Aufrufer selbst verantwortlich. Wenn da doppelte Namen verwendet werden, juckt das das Script in keinster Weise, da die Eingabedatei Zeile für Zeile abgearbeitet wird. Die Probleme kommen dann u.U. erst beim Parsen der Ergebnisse ...
Wie man das dann ziemlich leicht wieder in Shell-Variable umwandelt (eval) und es im weiteren Verlauf dann auseinandernimmt und passend wieder zusammensetzt, überlasse ich der Phantasie des Lesers. Ich wollte nur zeigen, daß es jenseits des "üblichen" ctlmgr_ctl noch andere Wege gibt, an AVM-Firmwareeinstellungen zu gelangen und dabei unter anderem auch auf Werte zuzugreifen, die ansonsten über das ctlmgr-Interface nicht ohne weiteres abzufragen sind.
Man kann über das Lua-Interface auch Variablen setzen, dazu komme ich gleich. Der weitaus häufigere Fall dürfte die Abfrage von Einstellungen sein, vielleicht hilft es ja jemandem weiter. Einiges von den Einstellungen läßt sich auch Stück für Stück über einzelne Abfragen per ctlmgr_ctl ermitteln, bei mir macht sich aber z.B. der Unterschied zwischen der einzelnen Abfrage der Einstellungen für >20 VPN-Verbindungen (jeweils 10 Einstellungen) und der "kompakten Listenabfrage" (so auch im AVM-GUI zu finden)
Rich (BBCode):
VPN=vpn:settings/connection/list(activated,name,deletable,editable,state,remote_ip,src,dst,connected_since,settings)
Zum Setzen von Einstellungen kann man das folgende Script verwenden:
Rich (BBCode):
#! /bin/luavar
lineno = 0;
rc = 0;
cmtable = {};
while true do
local line = io.read("*line");
if (line == nil) then
if (lineno == 0) then
io.stderr:write("Missing set statements\n");
os.exit(2);
else
break;
end
end
lineno = lineno + 1;
local varname, value = string.match(line, "(.*)=(.*)");
if (varname ~= nil and value ~= nil) then
table.insert(cmtable, { ["name"] = varname, ["value"] = value } );
else
io.stderr:write("Malformed set statement at line ",lineno,"\n");
os.exit(127);
end
end
err = 0;
message = "";
err, message = box.set_config(cmtable);
if (err == 0) then
io.stderr:write("OK\n");
rc = 0;
else
if (string.len(message) > 0) then
io.stderr:write(message,"\n");
else
io.stderr:write("error\n");
end
rc = 1;
end
os.exit(rc);
Rich (BBCode):
varname=value
Sollte beim Setzen von Einstellungen ein Fehler auftreten und eine Fehlermeldung dazu vom Variableninterface zurückgegeben werden, wird diese auf stderr ausgegeben und ein Exit-Code von 1 gesetzt. Ob und wie weit dann Zuweisungen teilweise ausgeführt wurden, hängt nach meinen Tests von der Reihenfolge der Anweisungen ab ... ich würde die Vermutung wagen, daß beim ersten Fehler abgebrochen wird.
Auch hier mal ein Beispiel, wir setzen eine neue statische IPv4-Route:
Rich (BBCode):
active=1
ipaddr=192.168.100.0
netmask=255.255.255.0
gateway=192.168.178.2
eval $(echo "newId=route:settings/route/newid" | queries.lua)
echo -e "route:settings/$newId/activated=$active\nroute:settings/$newId/ipaddr=$ipaddr\nroute:settings/$newId/netmask=$netmask\nroute:settings/$newId/gateway=$gateway" | set.lua
echo "rc = $?" 1>&2
Rich (BBCode):
#! /bin/sh
name=$1
shift
echo "$name=$*" | set.lua
exit $?
Zuletzt bearbeitet: