Stałe nazwy urządzeń w OpenWRT (hotplug, udev)

Spis treści

Bezprzewodowy router WiFi to w miarę proste urządzenie, które w zasadzie realizuje kilka podstawowych aspektów pracy sieci domowej. Wielu użytkownikom jednak jest nieustannie potrzebna jakaś nowa funkcjonalność, której oryginalny firmware producenta nie oferuje. Dlatego też mamy do dyspozycji OpenWRT będący minimalistyczną formą bardziej rozbudowanej dystrybucji linux'a. Może i OpenWRT daje nam możliwość zaawansowanej konfiguracji naszej sieci ale tego typu opcja powoduje też szereg problemów. Chodzi o to, że kernel dynamicznie tworzy nazwy dla wszystkich podłączanych urządzeń do routera. W dużych dystrybucjach linux'a do ogarnięcia tych nazw wykorzystywany jest UDEV. W przypadku OpenWRT też możemy skorzystać tego mechanizmu. Jeśli jednak mamy niewiele miejsca na pamięci flash routera, to możemy też skorzystać ze zdarzeń hotplug. W tym wpisie postaramy się przepisać nazwy pendrive/dysków twardych oraz modemów USB (LTE), tak by ich kolejność podłączania do routera nie stwarzała problemów w konfiguracji.

Problematyczne nazwy urządzeń

Jak zostało już nadmienione w wstępie, kernel linux'a musi jakoś oznaczyć urządzenia, do których się odwołują programy. Wszystkie te urządzenia znajdują się w katalogu /dev/ . Po podpięciu do routera, np. pendrive, system je rozpoznaje i udostępnia nam kilka interfejsów (pliki w katalogu /dev/ ). Ile tych interfejsów zostanie dodanych, to już zależy generalnie od sprzętu, który zostanie podłączony. Niemniej jednak, wspólną cechą nazewniczą jest podbijanie numerka końcowego danej klasy urządzeń. Dla przykładu weźmy sobie wspomniany pendrive lub dysk twardy. Pierwszy dysk zostanie rozpoznany jako sda , drugi zaś sdb , itd. Każda partycja na takim nośniku jest numerowana od 1 wzwyż. W taki sposób otrzymujemy sda1 , sda2 , sdb1 , sdb2 . Podobnie sprawa wygląda w przypadku modemów USB. Z tym, że tutaj mamy nazwy typu ttyUSB0 , ttyUSB1 , itd. Logiczne jest zatem, że gdy w konfiguracji demona, który działa na routerze, określimy ścieżkę do urządzenia jako /dev/ttyUSB1 , to przy innej kolejności podłączenia urządzeń, ten demon będzie odwoływał się nie do tego sprzętu co trzeba.

Oczywiście, w przypadku routerów zwykle nie podłączamy całej masy dodatkowego sprzętu, tak jak to ma miejsce przy standardowych PC. Niemnie jednak, OpenWRT zachęca do eksperymentów i ani się nie obejrzymy, a taki router pod względem funkcjonalności zacznie przypominać nasz desktop. Wypadałoby zatem pomyśleć już zawczasu, jak rozwiązać kwestię nazywania sprzętu oraz tego, by te nazwy były stałe i powiązane z konkretnym urządzeniem. Wtedy mielibyśmy pewność, że konfiguracja, np. demona wysyłającego i odbierającego SMS, działa bez względu na to w jakiej kolejności podłączymy urządzenia do routera.

W OpenWRT mamy dostępny pakiet udev i można skorzystać z tego narzędzia pisząc odpowiednie reguły, które przepiszą nazwy urządzeń. Niemniej jednak, nie jest to jedyny sposób. Szukając rozwiązania, trafiłem także na ten wpis. Zdaje się on adresować po części ten problem, któremu i nam przyszło stawić czoła. W podlinkowanym artykule jest jedynie informacja dotycząca bezpośrednio samych modemów USB ale nam to raczej nie powinno przeszkadzać i myślę, że uda nam się wypracować jakieś rozsądne rozwiązanie ułatwiające operowanie na urządzeniach podłączanych do routera domowego.

Zmiana nazwy urządzeń w OpenWRT przy pomocy UDEV'a

Jednym z bardziej wyrafinowanych rozwiązań jest zaimplementowanie na routerze mechanizmu UDEV. Wiąże się to z zainstalowaniem kilku pakietów ważących łącznie około 300-400 KiB. Jeśli mamy tyle miejsca na flash'u, to wgrajmy sobie te poniższe pakiety (zależności zostaną automatycznie pociągnięte):

# opkg update
# opkg install udev blkid

W wersji Chaos Calmer jest jaki błąd związany z blkid . Chodzi generalnie o ścieżkę dostępu do pliku wykonywalnego. W efekcie w logu UDEV'a pojawia się ten poniższy komunikat:

[2252.968182] [2080] spawn_read: '/sbin/blkid -o udev -p /dev/sda'(err) 'failed to execute '/sbin/blkid' '/sbin/blkid -o udev -p /dev/sda': No such file or directory'

Plik o który chodzi to /sbin/blkid ale pakiet blkid wgrywa ten plik do /usr/sbin/blkid . Możemy to w prosty sposób poprawić linkując ten plik do wskazanej lokalizacji:

# ln -s /usr/sbin/blkid /sbin/blkid

Problem jest też w samym UDEV'ie, bo ten nie ma dostarczanego skryptu startowego. W efekcie demon udevd nie jest uruchamiany wraz ze startem routera. Musimy zatem sobie taki skrypt startowy napisać. W tym celu tworzymy plik /etc/init.d/udevd i wrzucamy do niego poniższy kod:

#!/bin/sh /etc/rc.common

START=05

start() {
    /sbin/udevd --daemon
}

Nadajemy skryptowi prawa wykonywania ( chmod +x ) oraz dodajemy go do autostartu:

# /etc/init.d/udevd enable

Zmiana nazwy pendrive i dysku twardego

Poniżej jest przykład rozpoznania przez system routera pendrive (log pochodzi z logread ):

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

Mamy zatem dysk sda z trzema partycjami sda1 , sda2 oraz sda3 . W przypadku, gdy chcemy, by te poszczególne partycje miały prefiks king , musimy utworzyć stosowne reguły dla UDEV'a. Nie będę tutaj opisywał całej procedury tworzenia reguł ale jeśli ktoś jest ciekaw jak ten proces przebiega, to zapraszam pod podlinkowany wyżej artykuł. Tutaj ograniczymy się jedynie do stworzenia pliku reguły w /etc/udev/rules.d/90-dysk.rules . Do tego pliku wrzucamy poniższą zawartość:

ACTION=="add", SUBSYSTEM=="block", \
    ENV{ID_SERIAL_SHORT}=="0019E06B9C8ABE41C7A2C3EC", \
    SYMLINK+="king%n"

Zmienna ENV{ID_SERIAL_SHORT} odpowiada za numer seryjny pendrive. Możemy go odczytać w poniższy sposób:

# udevadm info --name /dev/sda --attribute-walk | grep -i serial
    ATTRS{serial}=="0019E06B9C8ABE41C7A2C3EC"

Po uzupełnieniu pliku, musimy jeszcze przeładować reguły UDEV'a:

# udevadm control --reload

No i oczywiście wyciągamy i podłączamy ponownie pendrive. Po chwili nowe nazwy urządzeń powinny widnieć w katalogu /dev/ :

# ls -al /dev/king*
lrwxrwxrwx    1 root     root             3 Jun 20 13:10 /dev/king -> sda
lrwxrwxrwx    1 root     root             4 Jun 20 13:13 /dev/king1 -> sda1
lrwxrwxrwx    1 root     root             4 Jun 20 13:14 /dev/king2 -> sda2
lrwxrwxrwx    1 root     root             4 Jun 20 13:14 /dev/king3 -> sda3

Zmiana nazwy modemu

Drugim popularnym urządzeniem podpinanym do portów USB routera jest modem LTE. W tym przypadku mamy model Huawei E3372s-153 w wersji NON-HiLink. Jest on wykrywany przez OpenWRT w poniższy sposób:

kern.info kernel: [ 4118.360000] usb 1-1.2: new high-speed USB device number 9 using ehci-platform
kern.info kernel: option 1-1.2:1.0: GSM modem (1-port) converter detected
kern.info kernel: usb 1-1.2: GSM modem (1-port) converter now attached to ttyUSB0
kern.info kernel: option 1-1.2:1.1: GSM modem (1-port) converter detected
kern.info kernel: usb 1-1.2: GSM modem (1-port) converter now attached to ttyUSB1
kern.info kernel: usb-storage 1-1.2:1.3: USB Mass Storage device detected
kern.info kernel: scsi host6: usb-storage 1-1.2:1.3
kern.notice kernel: scsi 6:0:0:0: Direct-Access     HUAWEI   TF CARD Storage  2.31 PQ: 0 ANSI: 2
kern.notice kernel: sd 6:0:0:0: [sda] Attached SCSI removable disk

Widzimy wyżej, że zostały utworzone dwa interfejsy ttyUSB0 oraz ttyUSB1 . To właśnie te dwie nazwy musimy przepisać. Trzeba jednak pamiętać, że modem USB może mieć więcej interfejsów w zależności od konfiguracji urządzenia. Tak czy inaczej, podobnie jak w przypadku pendrive, musimy pokusić się o napisanie odpowiedniej reguły, która przepisze nazwy wszystkich interfejsów tego urządzenia. Tworzymy zatem plik /etc/udev/rules.d/90-modem.rules i wrzucamy do niego poniższą zawartość:

ACTION=="add", KERNEL=="ttyUSB?", SUBSYSTEM=="tty", \
    ENV{ID_VENDOR_ID}=="12d1", \
    ENV{ID_MODEL_ID}=="15b6", \
    SYMLINK+="huawei-E3372-%n"

Zmienne ENV{ID_VENDOR_ID} oraz ENV{ID_MODEL_ID} możemy odczytać w poniższy sposób:

# udevadm info --name /dev/ttyUSB0 --attribute-walk | egrep "idV|idP"
    ATTRS{idVendor}=="12d1"
    ATTRS{idProduct}=="15b6"
    ATTRS{idVendor}=="05e3"
    ATTRS{idProduct}=="0608"
    ATTRS{idVendor}=="1d6b"
    ATTRS{idProduct}=="0002"

Mamy tutaj kilka identyfikatorów. Nas jednak interesują te dwa pierwsze. Na koniec przeładowujemy konfigurację UDEV'a

# udevadm control --reload

Wyciągamy teraz modem LTE z portu USB i podłączamy go ponownie. W katalogu /dev/ powinny widnieć teraz poniższe urządzenia:

/# ls -al /dev/ | grep huawei
lrwxrwxrwx    1 root     root             7 Jun 20 14:22 huawei-E3372-0 -> ttyUSB0
lrwxrwxrwx    1 root     root             7 Jun 20 14:22 huawei-E3372-1 -> ttyUSB1

Zmiana nazwy urządzeń w OpenWRT przez zdarzenia hotplug

Jeśli nie mamy na flash'u routera wystarczającej ilości miejsca, to możemy odpuścić sobie sposób z przepisywaniem nazw przy pomocy UDEV'a. Alternatywą jest skorzystanie ze zdarzeń hotplug. W OpenWRT, hotplug zastępuje mechanizm UDEV'a, który zaimplementowaliśmy wyżej. Nie musimy też instalować żadnych dodatkowych pakietów.

Hotplug wykonuje skrypty zlokalizowane w katalogu /etc/hotplug.d/ w momencie zaistnienia określonego zdarzenia, np. podniesienie interfejsu sieciowego, przyciśnięcie jakiegoś przycisku na routerze, czy też wykrycie jakiegoś urządzenia. W w/w katalogu mamy kilka podkatalogów. Jako, że my chcemy przepisywać nazwy urządzeń podpiętych do portu USB, to interesuje nas katalog /etc/hotplug.d/usb/ (modemy) oraz /etc/hotplug.d/block/ (dyski/pendrive). W zależności od urządzenia, wybieramy odpowiedni folder i tworzymy w nim plik 10-env . Następnie wrzucamy do niego poniższą treść:

#!/bin/sh
echo "----" >> /tmp/env
env >> /tmp/env

W pliku /tmp/env znajdować się będzie szereg zmiennych, w oparciu o które będziemy mogli napisać skrypt.

Zmiana nazwy modemu

Podłączamy teraz do portu USB modem LTE i zaglądamy do pliku /tmp/env :

----
UDEV_LOG=3
USER=root
ACTION=add
SHLVL=2
HOME=/
SEQNUM=497
HOTPLUG_TYPE=usb
DEVPATH=/devices/platform/ehci-platform/usb1/1-1/1-1.2/1-1.2:1.2
DEVICENAME=1-1.2:1.2
LOGNAME=root
TERM=linux
SUBSYSTEM=usb
board=TL-WDR4300
PATH=/usr/sbin:/usr/bin:/sbin:/bin
MODALIAS=usb:v12D1p15B6d0102dc00dsc00dp00icFFisc03ip16in02
TYPE=0/0/0
INTERFACE=255/3/22
PRODUCT=12d1/15b6/102
PWD=/
DEVTYPE=usb_interface

Widzimy tutaj szereg zmiennych, które są przekazywane do skryptu, który ma się wykonać w momencie podłączenia modemu LTE do portu USB. W oparciu o te zmienne możemy zatem napisać instrukcję, która będzie aplikowana jedynie w przypadku konkretnego urządzenia. Poniżej jest przykładowy kod, który zapisujemy w /etc/hotplug.d/usb/10-serial :

#!/bin/sh
if [ "$DEVTYPE" = "usb_interface" ] && [ "$ACTION" = "add" ]; then
    for tty in /sys/$DEVPATH/ttyUSB*; do
        [ -d "$tty" ] || continue
        OLDD=${tty##*/}

        # to jest E3372s-153
        if [ "x$PRODUCT" = "x12d1/15b6/102" ]; then
            NEWD="modem_e3372s_"${DEVPATH##*.}
            rm /dev/$NEWD
            ln -s /dev/$OLDD /dev/$NEWD
        fi
    done
fi

Instrukcja warunkowa pod # to jest E3372s-153 odnosi się tylko tego konkretnego modemu. Jeśli mamy kilka różnych modemów USB, musimy dodać kolejne instrukcje i odpowiednio dostosować warunek. Efektem są poniższe dowiązania w katalogu /dev/ :

/# ls -al /dev/modem*
lrwxrwxrwx    1 root     root            12 Jun 20 15:14 /dev/modem_e3372s_0 -> /dev/ttyUSB0
lrwxrwxrwx    1 root     root            12 Jun 20 15:14 /dev/modem_e3372s_1 -> /dev/ttyUSB1

Zmiana nazwy pendrive i dysku twardego

Jeśli chcemy przepisać nazwy urządzeń takich jak pendrive czy dyski twarde przy pomocy zdarzeń hotplug, to musimy nieco bardziej wytężyć nasz umysł. Chodzi generalnie o to, że skrypt 10-env umieszczony w katalogu /etc/hotplug.d/block/ nie zwróci nam ani numeru seryjnego urządzenia, ani też żadnego identyfikatora (VendorID, ProductID). Poniżej są zmienne, które zostały zalogowane po podłączeniu pendrive do routera:

----
DEVNAME=sda3
USER=root
ACTION=add
SHLVL=2
HOME=/
SEQNUM=502
HOTPLUG_TYPE=block
MAJOR=8
DEVPATH=/devices/platform/ehci-platform/usb1/1-1/1-1.2/1-1.2:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda3
DEVICENAME=sda3
LOGNAME=root
TERM=linux
SUBSYSTEM=block
board=TL-WDR4300
PATH=/usr/sbin:/usr/bin:/sbin:/bin
MINOR=3
PWD=/
DEVTYPE=partition

Niemniej jednak, numer seryjny pendrive możemy wyciągnąć odpowiednio zmieniając ścieżkę w zmiennej $DEVPATH . Mając numer seryjny, możemy napisać warunek. Poniżej przykładowy skrypt, który umieszczamy w pod /etc/hotplug.d/block/10-pen :

#!/bin/sh

new_devpath="$(echo "$DEVPATH" | cut -d/ -f 1-7)"
serial="$(cat /sys/$new_devpath/serial)"

if [ "$serial" = "0019E06B9C8ABE41C7A2C3EC" ] && [ "$ACTION" = "add" ]; then

    old_dev="$DEVICENAME"
    new_dev="king"${DEVICENAME##sd[a-z]}

    rm /dev/$new_dev
    ln -s /dev/$old_dev /dev/$new_dev

fi

Problemy ze zdarzeniami hotplug

Wszystkie linki utworzone za pomocą mechanizmu hotplug nie są usuwane przy wyciąganiu urządzenia z portu USB. W ten sposób inne urządzenie podpięte do portu USB może przejąc te linki i o tym trzeba pamiętać. Niemniej jednak, gdy podłączymy urządzenie, do którego skrypt ma się odwołać, to te stare linki zostaną zaktualizowane.

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.