Extroot i whole_root (fullroot) pod OpenWRT

Spis treści

Domowe routery WiFi zwykle nie dysponują flash'em o dużej pojemności. W ogromnej części przypadków pamięć flash w takich urządzeniach nie przekracza 16 MiB. W zasadzie jest to wystarczająca ilość miejsca ale tylko w przypadku korzystania z oryginalnego firmware producenta routera. Gdy w grę wchodzi OpenWRT, to przy tak niewielkiej przestrzeni jest duże prawdopodobieństwo, że przy instalowaniu dodatkowych pakietów zwyczajnie zabraknie nam miejsca. Jeśli nasz router dysponuje chociaż jednym portem USB, to możemy rozszerzyć system plików routera do rozmiarów partycji pendrive, który zostanie podłączony. W ten sposób z tych 16 MiB może nam się zrobić, np. 1-2 GiB, a to już w zupełności wystarczy na instalację dowolnych pakietów z repozytorium OpenWRT. Cały ten zabieg nosi nazwę extroot (external root) lub whole_root (fullroot) i w tym wpisie prześledzimy procedurę tworzenia tego mechanizmu.

Różnice między extroot i whole_root

Gdy chodzi o rozbudowanie systemu plików routera, to mamy do wyboru dwie opcje. Możemy to zrobić w oparciu o extroot lub whole_root . Różnica między tymi dwoma mechanizmami polega na tym, że extroot pozwala na przeniesienie części systemu routera (tj. zmian) na zewnętrzny nośnik. W przypadku zaś whole_root jest przenoszony cały system routera. Dokładny opis działania tych mechanizmów można znaleźć tutaj. W obu tych powyższych przypadkach partycja na pendrive musi być sformatowana w systemie plików z rodziny EXT, zwykle EXT4. Jest to domyślny system plików stosowany na linux'ach.

Wady i zalety extroot i whole_root

Wydawać by się mogło, że zarówno extroot jak i whole_root to bardzo przyzwoite mechanizmy, który w znacznym stopniu przyczyną się do rozbudowy naszego routera, praktycznie za free. Trzeba mieć jednak na uwadze jedną rzecz. Jeśli zamontujemy na routerze taką partycję, to nie możemy wyłączać routera przez przycisk na jego obudowie. A już na pewno odpada nam możliwość wyciągnięcia wtyczki z kontaktu. Tego typu akcje mogą uszkodzić system plików na pendrive, co może wiązać się z utratą części lub wszystkich plików zgromadzonych na partycji. Router niczym się praktycznie nie różni w działaniu od zwykłego PC. Jest tylko nieco mniejszy i nie ma tych wszystkich podzespołów, które mają standardowe komputery. Dlatego też trzeba postępować z nim podobnie jak z ze zwykłym komputerem, a tych przecie nie wyłączamy via przycisk na zasilaczu, prawda? By wyłączyć router, który korzysta z extroot lub whole_root , musimy się zalogować się na niego przez SSH i położyć system przy pomocy polecenia:

# sync && poweroff

Po wydaniu tego powyższego polecenia, router pozornie będzie wyglądał na powieszony. Niby stracimy z nim łączność ale lampki będą dalej się świecić czy migać. W tym stanie, router może zostać pozbawiony zasiania bez stwarzania zagrożenia dla systemu plików na pendrive.

extroot oraz whole_root mają tę zaletę, że jeśli coś pochrzanimy w konfiguracji, to możemy wyciągnąć pendrive z portu USB i załadować funkcjonalny system z flash'a routera. Wszystkie zmiany, które przeprowadziliśmy mając podpięty pendrive zostaną cofnięte. Dodatkowo, jeśli dysponujemy jakąś dystrybucją linux'a, lub też posiadamy system live, to partycję pendrive można zamontować w takim systemie w celu przebadania zaistniałych problemów. W ten sposób będziemy w stanie edytować poszczególne pliki. Coś jak tryb failsafe, z tym, że podczas tej operacji, nasz router działa i jest w pełni funkcjonalny. Zatem, jeśli przeprowadzamy jakieś eksperymenty z nowym oprogramowaniem, dobrze jest pierw to robić na extroot/whole_root . Jeśli wszystko będzie w porządku, możemy tę funkcjonalność zaimplementować na flash'u routera.

Pamiętajmy też, że po aktywowaniu mechanizmu extroot będziemy mieć kompletnie świeży system po zresetowaniu routera. Wygląda to tak jakbyśmy go dopiero co flash'owali nową wersją firmware OpenWRT. Całą konfigurację trzeba będzie ustawiać od początku.

Kolejna ważna sprawa, to upgrade firmware. W przypadku, gdy mamy zamiar dokonać aktualizacji oprogramowania routera, musimy odłączyć pendrive i uruchomić system z flash'a routera. Dopiero wtedy możemy wgrać obraz z nowym firmware.

Ostatnia rzecz, to UUID partycji z extroot lub whole_root . Narzędzie block-mount tworzy plik /etc/.extroot-uuid na partycji pendrive. W nim umieszcza UUID partycji mtd zawierającej rootfs. Jest to jedna z partycji routera. W fazie boot, podczas aktywacji extroot/whole_root , block-mount próbuje sprawdzić aktualną wartość UUID partycji mtd i porównuje ją z tą zapisaną w pliku /etc/.extroot-uuid . Jeśli te dwie wartośći się różnią, partycja pendrive nie zostanie zamontowana. Jeśli natomiast chcielibyśmy korzystać tej partycji po flash'owaniu routera, to musimy ten plik pierw skasować.

Instalacja potrzebnych pakietów

By zaimplementować extroot/whole_root , musimy doinstalować kilka pakietów na routerze. Poniżej znajduje się pełna lista potrzebnych nam rzeczy:

# opkg update
# opkg install block-mount \
kmod-fs-ext4 \
kmod-crypto-crc32c \
kmod-usb-storage \
kmod-usb-core

Pakiet kmod-crypto-crc32c jest wymagany w przypadku wystąpienia błędu EXT4-fs (sda4): Cannot load crc32c driver. podczas montowania zasobu (w logread) .

Przygotowanie i konfiguracja partycji pod extroot i whole_root

Kluczem w tej całej zabawie z rozszerzaniem systemu plików routera jest utworzenie odpowiedniej partycji. Tę zaś możemy przygotować bez większego problemu z płytki live mającą na pokładzie jakąś dystrybucję linux'a. Możemy także doinstalować fdisk w OpenWRT i przeprowadzić proces tworzenia partycji na routerze. W tym wpisie ograniczymy się jedynie do utworzenia partycji via gparted z systemu live. Pendrive może zostać podzielony w dowolny sposób, np. tak jak to widać na poniższej fotce:

gparted-linux-live-partycja-extroot-whole_root

Mając przygotowaną partycję, wsadzamy pendrive do jednego z portów USB w routerze. Urządzenie powinno zostać wykryte przez system i stosownie oznaczone:

kernel: [ 3020.600000] usb 1-1.2: new high-speed USB device number 3 using ehci-platform
kernel: [ 3020.750000] usb-storage 1-1.2:1.0: USB Mass Storage device detected
kernel: [ 3020.770000] scsi host0: usb-storage 1-1.2:1.0
kernel: [ 3021.830000] scsi 0:0:0:0: Direct-Access     Kingston DataTraveler 3.0 PMAP PQ: 0 ANSI: 6
kernel: [ 3022.400000] sd 0:0:0:0: [sda] 15360000 512-byte logical blocks: (7.86 GB/7.32 GiB)
kernel: [ 3022.410000] sd 0:0:0:0: [sda] Write Protect is off
kernel: [ 3022.410000] sd 0:0:0:0: [sda] Mode Sense: 23 00 00 00
kernel: [ 3022.410000] sd 0:0:0:0: [sda] No Caching mode page found
kernel: [ 3022.420000] sd 0:0:0:0: [sda] Assuming drive cache: write through
kernel: [ 3022.470000]  sda: sda1 sda2 sda3 sda4
kernel: [ 3022.490000] sd 0:0:0:0: [sda] Attached SCSI removable disk

Partycja, którą będziemy w tym przypadku wykorzystywać, to sda4 . Przy pomocy block detect generujemy odpowiednią konfigurację, którą trzeba umieścić w pliku /etc/config/fstab :

# block detect > /etc/config/fstab

Następnie edytujemy plik /etc/config/fstab . Wszystkie partycje, które zostały rozpoznane przez OpenWRT, zostaną automatycznie uwzględnione w tym pliku i umieszczone w sekcjach config 'mount' . Poza tymi sekcjami, mamy także ustawienia globalne, które są w sekcji config 'global' . Na początek omówmy sobie sekcję z ustawieniami globalnymi. Standardowo ta sekcja wygląda mniej więcej tak:

config 'global'
      option  anon_swap       '0'
      option  anon_mount      '0'
      option  auto_swap       '1'
      option  auto_mount      '1'
      option  delay_root      '5'
      option  check_fs        '0'

Opcje anon_swap oraz anon_mount odpowiadają za montowanie zasobów, które nie posiadają konfiguracji w pliku /etc/config/fstab . Nas one nie dotyczą, a poza tym, lepiej nie ruszać ich wartości domyślnych. Następnie mamy opcje auto_swap oraz auto_mount , które montują zasoby określone w pliku /etc/config/fstab i mające status enabled (o tym za moment). Jeśli chodzi zaś o opcję delay_root , to ma ona kluczowe znaczenie w przypadku extroot/whole_root . Może się tak zdarzyć, że nasz pendrive zostanie wykryty z pewnym opóźnieniem podczas startu systemu routera. W takim przypadku partycja nie zostanie zamontowana. Wartość tego parametru jest w sekundach i oznacza ile czasu system ma czekać zanim zamontuje partycję z extroot/fullroot. Ostatnia opcja, tj. check_fs , odpowiada za sprawdzanie systemu plików aktywnych zasobów.

Dalej w pliku mamy sekcje config 'mount' . Ta odpowiedzialna za extroot/fullroot jest przedstawiona poniżej:

config 'mount'
      option  target  '/overlay'
#     option  target  '/'
#     option  device  '/dev/sda4'
      option  uuid    'a2fc6334-0021-4468-8c21-48b2d2728ca2'
      option  fstype  'ext4'
      option  enabled '1'

Opcja target określa gdzie zamontować daną partycję. W przypadku extroot korzystamy z /overlay . Natomiast jeśli chodzi o whole_root , to dajemy tutaj / . W obu przypadkach wszelkie zmiany jakich dokonujemy w systemie, będą zapisywane partycji określonej w device. Niemniej jednak, w przypadku podpięcia innego pendrive, OpenWRT będzie próbował zamontować jedną z jego partycji. Dlatego też, lepiej unikać definiowania zasobów w taki sposób. Zamiast tego, lepiej jest wykorzystać opcję uuid , która identyfikuje konkretne urządzenie (partycję) podpięte do portu USB routera. Następnie mamy opcję fstype , która określa system plików partycji. Opcja enabled ustawiona na 1 sprawi, że zasób zostanie zamontowany w fazie boot routera.

Jeśli nie chce nam się od początku konfigurować routera, możemy zgrać całą konfigurację. W przypadku extroot ten krok jest opcjonalny. Natomiast jeśli chodzi o whole_root , to jest to wymagana procedura.

Przy przenoszeniu konfiguracji należy pamiętać, aby w pliku /etc/config/fstab przy sekcjach config 'mount' z extroot/whole_root przestawić poniższy parametr:

option  enabled '0'

Jeśli zapomnimy to zrobić, wtedy router się zapętli. Tę opcję zawsze można przestawić po skopiowaniu plików na partycję pendrive. Wystarczy skorzystać z polecenia mount .

Przenoszenie konfiguracji w przypadku extroot :

# mount /dev/sda4 /mnt/
# tar -C /overlay -cvf - . | tar -C /mnt/ -xf -
# umount /mnt/

Przenoszenie konfiguracji w przypadku whole_root :

# mount /dev/sda4 /mnt/
# mkdir -p /tmp/cproot/
# mount --bind / /tmp/cproot/
# tar -C /tmp/cproot -cvf - . | tar -C /mnt/ -xf -
# umount /tmp/cproot/
# umount /mnt/

Konfiguracja systemu na extroot

Po uruchomieniu routera, logujemy się na urządzenie standardowo za pomocą protokołu telnet. No chyba, że skopiowaliśmy sobie konfigurację. Tak czy inaczej, naszym oczom powinien się pokazać taki oto obrazek:

openwrt-extroot-whole_root

Jak widzimy, ilość wolnego miejsca na flash'u to 1,7 GiB.

Niżej zaś jest trochę informacji na temat systemu plików po aktywacji extroot , zwrócone przez df oraz mount :

# df -h
Filesystem                Size      Used Available Use% Mounted on
rootfs                    1.8G      5.6M      1.7G   0% /
/dev/root                 2.5M      2.5M         0 100% /rom
tmpfs                    61.5M     72.0K     61.5M   0% /tmp
/dev/sda4                 1.8G      5.6M      1.7G   0% /overlay
overlayfs:/overlay        1.8G      5.6M      1.7G   0% /
tmpfs                   512.0K         0    512.0K   0% /dev

/# mount
rootfs on / type rootfs (rw)
/dev/root on /rom type squashfs (ro,relatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,noatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime)
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noatime)
/dev/sda4 on /overlay type ext4 (rw,relatime,data=ordered)
overlayfs:/overlay on / type overlay (rw,noatime,lowerdir=/,upperdir=/overlay/upper,workdir=/overlay/work)
tmpfs on /dev type tmpfs (rw,nosuid,relatime,size=512k,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,mode=600)
debugfs on /sys/kernel/debug type debugfs (rw,noatime

Konfiguracja systemu na whole_root

W przypadku whole_root , informacje zwracane przez df i mount prezentują się następująco:

# df -h
Filesystem                Size      Used Available Use% Mounted on
rootfs                    1.8G     14.0M      1.6G   1% /
/dev/root                 2.5M      2.5M         0 100% /rom
tmpfs                    61.5M     68.0K     61.5M   0% /tmp
/dev/sda4                 1.8G     14.0M      1.6G   1% /
tmpfs                   512.0K         0    512.0K   0% /dev

# mount
rootfs on / type rootfs (rw)
/dev/root on /rom type squashfs (ro,noatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,noatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime)
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noatime)
/dev/sda4 on / type ext4 (rw,relatime,data=ordered)
tmpfs on /dev type tmpfs (rw,nosuid,relatime,size=512k,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,mode=600)
debugfs on /sys/kernel/debug type debugfs (rw,noatime)

To co odróżnia whole_root od extroot , to fakt, że w tym pierwszym nie mamy wpisu overlayfs:/overlay .

Partycja rootfs_data (mtdblock3, mtd3)

Czasem przy korzystaniu z extroot/whole_root może zajść potrzeba, by podejrzeć pewne pliki systemowe, które utworzyliśmy czy zmieniliśmy zanim przeszliśmy na któryś z tych powyższych mechanizmów. Część użytkowników w takiej sytuacji resetuje router i odpala go bez podpiętego pendrive. Jednak nie ma takiej potrzeby, bo partycję rootfs_data (tę zawierającą zmiany w obrazie firmware) można bez problemu zamontować sobie z poziomu extroot/whole_root . Musimy tylko sprawdzić, która to jest partycja. Zwykle jest to mtd3 ale lepiej jest się upewnić zaglądając do pliku /proc/mtd . W przypadku routera TP-LINK TL-WDR3600 v1 ten plik prezentuje się następująco:

# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00020000 00010000 "u-boot"
mtd1: 00121a00 00010000 "kernel"
mtd2: 006ae600 00010000 "rootfs"
mtd3: 00440000 00010000 "rootfs_data"
mtd4: 00010000 00010000 "art"
mtd5: 007d0000 00010000 "firmware"

Widzimy tutaj wyraźnie, że rootfs_data ma przypisane urządzenie mtd3 i w katalogu /dev/ również mamy taki plik. Niemniej jednak, jest to urządzenie znakowe i nie możemy z niego skorzystać. Zamiast tego musimy posłużyć się emulacją urządzenia blokowego /dev/mtdblock3 . Dokładna różnica między urządzeniami mtd i mtdblock jest wyjaśniona tutaj. Najważniejsza rzecz, to unikać zapisu tego urządzenia. Wszelkie próby kontaktu z /dev/mtdblock3 powinny być tylko w trybie do odczytu. Niemniej jednak, jest możliwe operowanie na tym urządzenie jak na zwykłej partycji, a przez to możemy ją zamontować w poniższy sposób:

# mount -t jffs2 -o ro /dev/mtdblock3 /overlay-boot/

Możemy także wykorzystać do tego celu block-mount . Choć trzeba pamiętać, że partycje montowane za jego pomocą będą w trybie do zapisu. Tak czy inaczej jeśli chcemy by partycja /dev/mtdblock3 była montowana na starcie routera, to do pliku /etc/config/fstab dodajemy tę poniższą treść:

config 'mount'
      option target '/overlay-boot'
      option device '/dev/mtdblock3'
      option fstype 'jffs2'
      option enabled '1'
Mikhail Morfikov avatar
Mikhail Morfikov
Po ponad 10 latach spędzonych z różnej maści linux'ami (Debian/Ubuntu, OpenWRT, Android) mogę śmiało powiedzieć, że nie ma rzeczy niemożliwych i problemów, których nie da się rozwiązać. Jedną umiejętność, którą ludzki umysł musi posiąść, by wybrnąć nawet z tej najbardziej nieprzyjemniej sytuacji, to zdolność logicznego rozumowania.