Montag, 20. April 2009

UPnP – Entdeckungsreise... zu Fuß

Für die im Folgenden beschrieben Abläufe gibt es sicher spezialisierte Programme. Das Ziel war aber ein kurzes Skript, das von mrtg aufgerufen werden soll. Hierzu ist nur die Kenntnis einer Handvoll Strings notwendig, die in das Skript integriert werden müssen.

Auch will ich hier nicht das UPnP-Protokoll vollständig wiedergeben. Vielmehr werden nur die Teile vorgestellt, die zur Abfrage von Werten (z.B. der Anzahl der übertragenen Bytes) benötigt werden.

Mit diesen Kenntnissen sollte sich ng-upnp2mrtg eigentlich an alle UPnP-Modems, die die UPnP-Abfrage unterstützen anpassen lassen.

Die eigentliche Abfrage

Nachdem eine TCP-Verbindung zum UPnP-Server im Modem aufgebaut ist, erfolgt die Kommunikation nach dem HTTP-Protokoll.

Schauen wir uns eine typische Abfrage an.

Der Client sendet:
POST /WANCommonInterfaceConfigService/control HTTP/1.0
HOST: 192.168.0.1:49300
CONTENT-LENGTH: 340
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1#GetTotalBytesReceived"

<?xml version="1.0"?>
<s:Envelope
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetTotalBytesReceived xmlns:u="urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1">
</u:GetTotalBytesReceived>
</s:Body>
</s:Envelope>
  • Die erste Zeile ist ein HTTP-Befehl (eine Methode, gefolgt von einer URL, gefolgt von der Protokollversion).
  • Ihr folgen einige Header-Zeilen in der Form 'Schlüssel: Wert'.
  • Eine Leerzeile trennt den Header von der eigentlichen Nachricht.
  • Diese liegt im XML-Format vor (SOAP).
Die interessanten variablen Teile sind fett dargestellt. Dies sind zum einen die URL in der ersten Zeile, und in der Zeile SOAPACTION den – nennen wir ihn – Dienstbezeichner (vor dem #-Zeichen mit 'urn:schemas-upnp.org' beginnend) und die darauf anzuwendende Aktion (hinter dem #).

Natürlich müssen auch die Werte für HOST und CONTENT-LENGTH angepasst werden. Sie haben die im HTTP-Protokoll definierten Bedeutungen und sind selbsterklärend.

Die variablen Teile in der XML-Nachricht kennen wir schon aus dem Header.

In der gleich TCP-Verbindung liefert der Server seine Antwort. Hier wieder ein typisches Beispiel:
HTTP/1.1 200 OK
EXT:
CONTENT-TYPE: text/xml; charset="utf-8"
SERVER: IAD, UPnP/1.0, MicroStack v1.0.2777

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:GetStatusInfoResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewConnectionStatus>Connected</NewConnectionStatus>
<NewLastConnectionError>ERROR_NONE</NewLastConnectionError>
<NewUptime>30864</NewUptime>
</u:GetStatusInfoResponse>
</s:Body>
</s:Envelope>
Man erkennt eine Standard-HTTP-Antwort, mit dem HTTP-Ergebniscode in Zeile 1, weitere Headerzeilen, die unvermeidliche Leerzeile, gefolgt von Text, wieder im XML-Format. Im <u>-Tag selbst findet man wieder die Werte für Aktion und Dienst-Bezeichner. Der <u>-Tag umschließt die gewünschten Information, die ihrerseits von <tag> ...</tag> eingeschlossen sind.

Der Ergebniscode hat die bei HTTP definierte Bedeutung – kurz gesagt: 200 ist ok, bei allen anderen Werten muss man nachforschen.

Um also eine bestimmte Information abzufragen braucht man:
  • IP-Adresse und Port-Nummer des UPnP-Servers
  • die URL
  • den Bezeichner für den Dienst bzw. das Gerät und die Aktion
  • sowie den Namen des Tags, in dem in der Antwort die gewünschte Information steht.
Die IP-Adresse ist meist mit der des Routers identisch, doch spätestens bei der Port-Nummer wird es schwierig. So verwendet die FritzBox Port 49000, das NetCologne-Modem hingegen Port 49300.

Der Weg in den Router

Glücklicherweise werden diese Informationen – und noch einige mehr – vom UPnP-Server per UDP-Broadcast verbreitet. Standardmäßig wird dieser Broadcast an die IP-Adresse 239.255.255.250. Port 1900 gerichtet. Das Protokoll heißt SSDP (Simple Service Discovery Protocol).

Die Pakete kann man z.B. mit Wireshark auffangen und decodieren. Grenzen Sie ggf. die Anzeige mit 'ip.addr == a.b.c.d' auf die IP des Routers ein.

Lassen Sie sich Zeit. Es kann einige Minuten dauern, bis die ersten SSDP-Pakete angezeigt werden. Untersuchen Sie den Text-Inhalt. Nach einiger Zeit wird dieser sich wiederholen, Sie sollten aber mehrere unterschiedliche Pakete finden.

Hier der Textinhalt eines solchen Pakets
NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
LOCATION: http://192.168.0.99:49000/igddesc.xml
SERVER: mynet UPnP/1.0 AVM FRITZ!Box WLAN 3170 49.04.57
CACHE-CONTROL: max-age=1800
NT: urn:schemas-upnp-org:device:InternetGatewayDevice:1
NTS: ssdp:alive
USN: uuid:75802409-bccb-40e7-8e6c-001C4A50656D::urn:schemas-upnp-org:device:InternetGatewayDevice:1
Auch hier erkennt man wieder die HTTP-Struktur: eine Methode (NOTIFY), eine URL (*) (naja) und die Protokollversion, gefolgt von UPnP-Headern, die diverse nützliche Informationen enthalten.
  • LOCATION: eine URL mit einer XML-Datei, die die Dienste weiter beschreibt.
  • NT: den „Dienst-Bezeichner“
  • NTS: ssdp:alive = das Geräte meldet sich an; ssdp:byebye = das Gerät meldet sich ab
  • USN: nochmal der Name des Diensts, diesmal mit einer eindeutigen ID, die von Gerät zu Gerät unterschiedlich ist, auch wenn die Dienstnamen identisch sind.
Tell me more...

Der nächste Schritt ist, die URL aus LOCATION in den Browser einzugeben. Ergebnis ist eine weitere XML-Datei. (Wenn Sie hierzu Firefox verwenden, wird die Datei übersichtlich formatiert dargestellt.) Interessant sind die Einträge unter <serviceList>, die an mehreren Stellen und in unterschiedlichen Ebenen im XML-Baum auftreten können:

Hier ein Beispiel:
...
<serviceList>
<service>
<serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType>
<serviceId>urn:upnp-org:serviceId:WANCommonIFC1</serviceId>
<SCPDURL>WANCommonInterfaceConfigService/scpd.xml</SCPDURL>
<controlURL>WANCommonInterfaceConfigService/control</controlURL>
...
</service>
...
</serviceList>

Unterhalb von <serviceList> findet man diverse Dienste mit <service> aufgelistet.
  • Der <serviceType> ist der Dienst-Bezeichner, den wir in der UPnP-Abfrage brauchen.
  • Die <controlURL> ist die URL für die UPnP-Abfrage (ohne vorangestellten Backslash).
  • <SCPDURL> verweist auf eine weitere XML-Datei, die die Aktionen näher beschreibt.
Stellen Sie der <SCPDURL> 'http://IP-Adresse:Port/' voran und geben Sie sie im Browser ein.

In dieser XML-Datei findet sich unter <actionList> ein oder mehrere Einträge <action>. Eine Ebene tiefer finden sich <name> und die <argumentList> mit einem oder mehreren <argument> Einträgen. Und bei <argument> interessieren wir uns vor allem für <name> und <direction>.

Prinzipieller Aufbau:
<scpd>
...
<actionList>
<action>
<name>Name_der_Aktion</name>
<argumentList>
<argument>
<name>ArgumentName_1</name>
<direction>in</direction>
</argument>
<argument>
<name>ArgumentName_2</name>
<direction>out</direction>
</argument>
...
</argumentList>
</action>
...
</actionList>
</scpd>

Die Namen der Tags sind selbsterklärend. Interessant für eine Abfrage sind natürlich nur die Aktionen mit der <direction> out.
  • Der Name der <action> ist der Aktionsname für unsere Abfrage.
  • Der Name bei <argument> ist der Name des Tags bei der Antwort, der den entsprechenden Wert einklammert.
Somit hätten wir also alle Informationen zusammen, die wir für die Abfrage brauchen.

Zum Abschluss

Die Stellen in ng-upnp2mrtg, an denen diese Werte eingetragen werden müssen, sind im Quelltext einfach zu finden.
Die Byte-Counter befinden sich meist in einer Aktion, die als Antwort die Tags „NewTotalBytesReceived“ und „NewTotalBytesReceived“ zurück gibt. Für die Verbindungsdauer sucht man am besten nach „NewUpTime“. Alle Werte werden bei einem Verbindungsaufbau zurückgesetzt, was bei MRTG leider einen Spike erzeugt.

2 Kommentare:

hildi hat gesagt…

Ich habe eine Fritz!Box 6591 Cable. Nach allem, was ich hier nachgelesen habe, sollte es wie bei der 7000er funktionieren und es kommen auch ohne Fehler Werte zurück.

Die MRTG Werte bekomme ich aber mit den Anzeigen in der Fritz!Box nicht zusammen. Auch wenn ich mal einige Minuten die Leitung absichtlich komplett auslaste, ist das in der MRTG Grafik nicht zu sehen.

Irgendeine Idee, wie ich das debuggen könnte?

Michael hat gesagt…

Für eine Ferndiagnose enthält Dein Kommentar nicht genug Informationen. :-)
Am besten meldest Du Dich mal per E-Mail.... steht an der Seite.