Blog von Michael Wyraz

TP-Link Festa F52-Outdoor in Omada EAP225-Outdoor verwandeln

This article is also available in English.

Bei der Recherche nach einem gĂĽnstigen Outdoor Access-Point bin ich bei Amazon ĂĽber den TP-Link Festa F52-Outdoor fĂĽr weniger als 30 Euro gestolpert.

Immer wenn ich Router und ähnliches kaufe, checke ich, ob das Gerät potenziell auch von OpenWrt unterstützt wird. Offiziell gibt es noch keinen Support, aber einen vielversprechenden offenen Pull Request, der mit folgenden Worten beginnt:

The TP-Link Festa F52-Outdoor v1 consists of the same hardware as the TP-Link EAP225-Outdoor v3. Only the device identifiers differ.

Der Omada EAP225-Outdoor v3 ist mit 80€ fast 3x so teuer wie der Festa F52-Outdoor. Deshalb habe ich mich gefragt, ob Identifier und Software wirklich der einzige Unterschied sind und ob sich das eine Gerät in das andere upgraden lässt.

Innenleben

Das Öffnen der Geräte ist denkbar wartungsfreundlich. Nachdem die Antennen abgeschraubt und der Dichtgummi abgezogen wurde, muss ein goldener Schraubring abgedreht werden. Dazu einfach mit einem schmalen Schraubendreher in die Ritze gehen und mit etwas Druck drehen.

Die 2 Ringe abschrauben, schon ist der AP offen
Die 2 Ringe abschrauben, schon ist der AP offen

Sind die Ringe entfernt, können die Antennen vorsichtig reingedrückt werden. Das Gehäuse lässt sich jetzt aufschieben. Zum Vorschein kommt ein schlankes Board, welches sich zwischen beiden Modellreihen tatsächlich nicht unterscheidet.

Bis auf die Farbe des Kondensators sind keine Unterschiede zu erkennen
Bis auf die Farbe des Kondensators sind keine Unterschiede zu erkennen

An der rechten Seite sind Kontaktstellen zu sehen, die mit freundlichem GND, RX, TX und VCC beschriftet sind. Die serielle Schnittstelle ist gefunden.

Mit einer Programmierzange werden die Kontakte kontaktiert
Mit einer Programmierzange werden die Kontakte kontaktiert

Verbunden werden nur GND, RX und TX mit einem 3,3V USB-zu-Seriell-Adapter. Nach dem Einschalten erscheint ein Bootlog, welches aber nach ein paar Zeilen an Stack Pointer at: 87f83f98 hängen bleibt. Ohne die serielle Schnittstelle bootet es hingegen problemlos.

Nach ein wenig Tüfteln habe ich die Lösung: zwischen TX des Boards und RX meines USB-zu-Seriell-Adapters kommt noch ein 680Ω Widerstand. Damit ist der Pull am TX nicht mehr so stark und das Board bootet.

Software

Auch das Ermitteln der Baudrate war kniffelig. Eine KI-Analyse des Datenstroms würfelt 120192 Baud, was tatsächlich funktioniert. Ich bekomme ein erstes Log des Bootloaders

U-Boot 1.1.4--LSDK-10.2-00082-4-gc89d0445-dirty (Mar 27 2024 - 20:14:46)

board956x - Dragonfly 1.0DRAM:  
sri
ath_ddr_initial_config(287): (ddr2 init)
ath_ddr_initial_config: DDR_EMR2_ADDRESS=0x180000bc, EMR2 value=0x80
ath_sys_frequency: ref_clk 25000000
ath_sys_frequency: cpu 775 ddr 650 ahb 258
Tap values = (0x10, 0x10, 0x10, 0x10)
128 MB
Top of RAM usable for U-Boot at: 88000000
Reserving 165k for U-Boot at: 87fd4000
Reserving 192k for malloc() at: 87fa4000
Reserving 44 Bytes for Board Info at: 87fa3fd4
Reserving 36 Bytes for Global Data at: 87fa3fb0
Reserving 128k for boot params() at: 87f83fb0
Stack Pointer at: 87f83f98
Now running in RAM - U-Boot at: 87fd4000
Flash Manuf Id 0x1c, DeviceId0 0x70, DeviceId1 0x18
flash size 16MB, sector count = 256
Flash: 16 MB
*** Warning - bad CRC, using default environment

In:    serial
Out:   serial
Err:   serial
Setting 0x181162c0 to 0x28502100
Hit Ctrl+B to stop autoboot:  2  1  0 
Loading .text @ 0x80351d10 (12496 bytes)
Loading .rodata.str1.4 @ 0x80354de0 (676 bytes)
Loading .data @ 0x80355090 (1432090 bytes)
Clearing .bss @ 0x804b2ab0 (4202512 bytes)
## Starting application at 0x80351d10 ...
BOOT CONFIG:     80208482
zimage at:     80355090 804B2AAA
Uncompressing Linux at load address 80060000
Now, booting the kernel...
[    0.000000] Linux version 3.3.8 (jenkins@sohoiapbuild) (gcc version 4.3.3 (GCC) ) #1 Fri Mar 29 14:34:42 CST 2024
[...]

Der Kernel wechselt nach einer Weile auf standardisierte 115200 Baud, ab da gibt es wieder Datenmüll auf der Konsole. Mit zwei Mitschnitten unterschiedlicher Baudrate lässt sich aber ein komplettes Bootlog zusammensetzen.

Der wichtigste Befund ist Hit Ctrl+B to stop autoboot: 2 1 0 . Leider keine Sekunden, sondern eher ein 100ms-Timer. Konsequentes Strg+B-Hämmern bringt mich aber in den Bootloader.

Ich kompiliere eine Ram-Version der OpenWrt-Firmware aus dem verlinkten Pull-Request. Diese lässt sich per TFTP laden und booten:

tftpboot 0x80800000 f52-openwrt-initramfs-RAM-ONLY.bin
bootelf 0x80800000

Von dem laufenden OpenWrt aus kann ich ein Backup aller Partitionen ziehen. Das erlaubt mir zum einen eine Analyse, zum anderen das gefahrlose Testen. Solange ich den Bootloader nicht ĂĽberschreibe, kann ich jederzeit in den jetzigen Zustand zurĂĽckkehren.

Das Backup erstelle ich von beiden Geräten, so dass ich beide vergleichen kann.

Partitionstabelle

MTD Name Start Ende Größe
0 u-boot 0x000000 0x020000 128 KiB
1 pation-table 0x020000 0x030000 64 KiB
2 product-info 0x030000 0x040000 64 KiB
3 kernel 0x040000 0x1c0000 1.500 KiB
4 rootfs 0x1c0000 0xf00000 13.568 KiB
5 config 0xf00000 0xf30000 192 KiB
6 mutil-log 0xf30000 0xfb0000 512 KiB
7 oops 0xfb0000 0xff0000 256 KiB
8 ART 0xff0000 0x1000000 64 KiB

Analyse

Beim Vergleich der Daten kam KI zum Einsatz. Nach ein paar Minuten standen die Unterschiede der Images fest. Zum Einen hat der Festa einen neueren Bootloader (was nicht wundert, mein EAP ist schon einige Jahre alt). Dann unterscheidet sich “product-info” - hier steht, um welchen Gerätetyp es sich handelt, aber auch Geräte-individuelle Daten wie MAC-Adresse und SchlĂĽsselmaterial.

Kernel und rootfs unterscheiden sich selbstverständlich in der installierten Software. Config etc. unterscheiden sich auch, die verschiebe ich aber auf später - im Zweifel lassen die sich mit Werksreset auf Linie bringen.

Product-Info ist recht einfach strukturiert:

Position innerhalb der Partition Inhalt
+0x0000 MAC-Adresse und individuelle Factory-Daten
+0x1000 SupportList: Welche Gerätekennung eine Firmware unterstützt
+0x1100 Der eigentliche Produktdatensatz
+0x2000 Installierte Firmwareversion und zusätzliche Versionsmetadaten

Der eigentliche Produktdatensatz beginnt bei +0x1100 und hat 1.024 Bytes Platz. Er besteht aus:

  • 4 Bytes: Länge des folgenden Dateninhalts
  • 4 Bytes: reserviert
  • danach: Produktdaten als Text
  • Rest: FĂĽllbytes

Die Länge steht als Big-endian-Zahl im Header – das höchstwertige Byte zuerst. Sie bezieht sich auf den Inhalt nach dem 8-Byte-Header, nicht auf den gesamten Block. Der Text beginnt deshalb bei +0x1108, absolut bei 0x031108.

Festa F52-Outdoor v1, Firmware 1.0.0

Festa F52-Outdoor(TP-Link|UN|AC1200-D):1.0
key=Bg********==
rsaKey=Bg********==
HWID=B2921ED2B6B2CBA95A06E53B79B2C463

Omada EAP225-Outdoor v3, Firmware 5.2.3

EAP225-Outdoor(TP-Link|UN|AC1200-D):3.0
key=Bg********==
rsaKey=Bg********==
HWID=96455746BD4C5113D5E35DAF2CC3CA9B

Die Keys sind vermutlich gerätespezifisch. Diese habe ich nicht angerührt. Die erste Zeile ist die Gerätetyp-Kennung. Diese ist relevant für das Funktionieren der Firmware. Die Festa-Firmware läuft nicht mit der Omada Kennung, die Omada-Firmware nicht mit der Festa-Kennung.

Die HWID hat scheinbar keine Relevanz, spätere Tests haben aber gezeigt, dass das Gerät mit falscher HWID unter Omada keine automatischen Updates bekommt. Manuelle Updates sind möglich, aber besser gleich die korrekte Geräte-ID setzen.

Upgrade

Um ein “artfremdes” Image auf einen Omada- oder Festa-Access-Point zu flashen, mĂĽssen zwei Bedingungen erfĂĽllt sein.

  1. Der Gerätetyp des Images (“SupportList”) muss mit der laufenden Systemkennung ĂĽbereinstimmen
  2. Die SignaturprĂĽfung des Access Points muss deaktiviert werden

Bei der Auswahl des Images habe ich konservativ 5.1.11 gewählt. Aktuell sind zum Zeitpunkt 5.2.2 bzw. 5.2.3. Die kleinere Version gibt mir die Möglichkeit, die Updatefähigkeit im Anschluss zu testen.

Die Modifikation des Images war ein weiterer KI-Job. Ich habe auf das OpenWrt-Projekt tplink-safeloader hingewiesen. Die KI hat kurzerhand ein StĂĽck Python gescripted, welches die “SupportList” im Image durch Festa F52-Outdoor(TP-Link|UN|AC1200-D):1.0 ersetzt, sonst aber alles unverändert lässt.

Um die Signaturprüfung des Access Points zu deaktivieren, wird in der Weboberfläche SSH aktiviert. Per SSH kann man sich nun einloggen und cliclientd stopcs eingeben. Die Signaturprüfung ist damit bis zum Reboot deaktiviert.

Jetzt kann das Image ĂĽber die Weboberfläche installiert werden. Omada bootet, findet aber keine zu seinem Gerätetyp passende Konfiguration, was zu einem “halben” System ohne Netzwerk fĂĽhrt.

Um das System ans Laufen zu bekommen, ist noch die product-info-Partition anzupassen. Per TFTP wird noch einmal OpenWrt gebootet und eine neue Kopie der Partition erstellt, da das Omada-Upgrade diese bereits modifiziert und die neu installierte Firmware eingetragen hat. Die Anpassung des Produkt-Blocks hat die KI fĂĽr mich erledigt. Das Schreiben geht dann wieder vom Bootloader aus:

tftpboot 0x84000000 product-info-modified.bin
erase 0x9f030000 +0x10000
cp.b 0x84000000 0x9f030000 0x10000

Nach dem Flashen der modifizierten product-info bootet das Gerät komplett und kann im Omada-Controller adoptiert werden.

Test

Das Adoptieren im Omada-Controller läuft wie gewohnt. Das Gerät meldet sich als “EAP225-Outdoor(EU) v3.0” und lässt sich ohne erkennbaren Unterschied zu den bestehenden Geräten managen.

Das modifizierte Gerät wird im Discovery gefunden …
Das modifizierte Gerät wird im Discovery gefunden …

… und wird anstandslos adoptiert
… und wird anstandslos adoptiert

Ein kurzer Test aller relevanten Funktionen hat keine Fehler oder Probleme gezeigt. Ein Upgrade auf 5.2.2 erfolgte manuell, da ich die HWID erst nicht geändert hatte und selbst nach dem Löschen und Neuadoptieren die alte ID gecached war. Das Upgrade auf 5.2.3 erfolgte dann mit korrekter HWID automatisch.

Downgrade

Zum Schluss habe ich noch den Rückweg zum Festa-Gerät geprüft. Dazu habe ich Partitionen 1-7 aus dem Backup via TFTP aus dem Bootloader heraus geflasht. Das Gerät hat danach wieder als normales Festa F52 gebootet.