Zdalny nadzór routera
Ostatnia zmiana: 2013-11-26 18:43

Czasami mamy do czynienia z routerami, które w jakiś sposób chcielibyśmy kontrolować, ale są one poza naszym fizycznym zasięgiem. Router u rodziny, na działce, w samochodzie, z modemem 3G, nie wiadomo czy i kiedy będzie włączony. Chcielibyśmy, aby takie urządzenie "zgłaszało" się samo i czasami można było także dać mu jakieś polecenie do wykonania.
Takie rozwiązanie zastosowano w projekcie Fon - zadaniem skryptu było cykliczne zgłaszanie się do systemów nadzoru, co pozwalało uzyskać informacje o fakcie działania routera oraz zdalnie nadzorować router przez zlecanie mu określonych poleceń do wykonania.
Można utworzyć skrypt, który zostanie cyklicznie uruchomiony. Zadaniem skryptu będzie zgłoszenie się pod określony adres, pobranie danych i jeżeli coś jest - wykonanie tego. Rezultat może być odesłany z powrotem. Podane rozwiązanie znakomicie sprawdza się np. na routerach z modemami 3G, do których nie można się bezpośrednio dostać, a czasami należało by mieć nad nimi kontrolę. Do działania niezbędne będzie posiadanie strony www która będzie przetwarzała wywołania klientów.

Skrypt klienta

Czyli skrypt, który cyklicznie będzie wykonywał się na zdalnym routerze. Do działania niezbędny będzie jeszcze program base64 który pozwoli na przetworzenie dowolnych danych do postaci czytelnego ciągu znaków ASCII.


    # opkg update
    # opkg install coreutils-base64

Tworzymy plik o nazwie remote.sh w katalogu /sbin


    # touch /sbin/remote.sh
    # chmod 777 /sbin/remote.sh

i umieszczamy w nim następujący skrypt:


    #!/bin/sh
    rm /tmp/skrypt.sh > /dev/null 2>&1
    OUT="/tmp/output.txt"
    MAC=$(ifconfig br-lan | awk '/HWaddr/ {gsub(":","");print $5}')
    P=""
    if [ -e "$OUT" ]; then
        P=$(cat "$OUT" | base64 -w0)
        rm "$OUT"
    fi
    wget -q -O /tmp/skrypt.sh "http://serwer.net/cgi-bin/heartbeat.cgi?mac=$MAC&p=$P"
    if [ -s /tmp/skrypt.sh ]; then
        sh /tmp/skrypt.sh > "$OUT" 2>&1
    fi
    exit 0

Parę słów wyjaśnienia. Klient wywołuje adres naszej strony, przekazując dwie dane w adresie URL: adres mac, pobrany z interfejsu br-lan który identyfikuje ten konkretny router (pozbawiony znaków ":"), oraz dodatkową zmienną, tzw. payload która zawiera wynik działania skryptu zleconego do wykonania przy poprzednim uruchomieniu (o ile jakiś wynik był). Żeby można było taki plik przesłać, jest on kodowany w base64, dzięki czemu całość jest po prostu ciągiem znaków. Wynikiem może być skrypt do wykonania zlecony przez serwer do którego zostały wysyłane dane. Jeżeli jest coś, to jest to wykonywane. Wynik wykonania tego skryptu jest zapisywany do pliku, który jako payload zostanie wysłany do serwera przy następnym uruchomieniu programu.
Uruchomienie skryptu można włożyć do crona, cyklicznie co np. 10min:


    # echo "*/10 * * * * /sbin/remote.sh" >> /etc/crontab/root
    # /etc/init.d/cron restart

Skrypt serwera

Zakładamy że dysponujemy serwerem pod domeną serwer.net. W nim mamy skrypt cgi, który będzie odbierał zgłoszenia od klientów i ew przekazywał im polecenia do wykonania. Rolę serwera może wykonywać np. OpenWrt z uruchomionym uhttpd i odpowiednim skryptem zapisanym w podkatalogu cgi_bin w katalogu głównym serwera www. Przykładowy taki skrypt (o nazwie heartbeat.cgi, bo taka nazwa jest zdefiniowana w skrypcie klienta), może wyglądać następująco:


    #!/bin/sh
    echo "Content-type: text/xml"
    echo ""
    eval $(echo "$QUERY_STRING"|awk -F'&' '{for(i=1;i<=NF;i++){print $i}}')

    date +'%Y-%m-%d %H:%M:%S' >> /tmp/log.txt
    echo "mac: $mac" >> /tmp/log.txt
    p=$(echo "$p" | base64 -d)
    echo "payload:" >> /tmp/log.txt
    echo "$p" >> /tmp/log.txt
    echo "----" >> /tmp/log.txt

W takiej postaci skrypt będzie zapisywał do pliku /tmp/log.txt informacje o zgłoszeniach klienta w postaci:


    2012-09-25 21:19:23
    mac: F8D111010203
    payload:

    
Aby wydać polecenie do zdalnego systemu należy uzupełnić skrypt heartbeat.cgi na serwerze o następujący kod (tu: dla routera o mac adresie F8D111010203):


    cmd=""
    case "$mac" in
        "F8D111010203")
                cmd="logread"
                ;;
    esac

    if [ -n "$cmd" ]; then
        echo -e "#!/bin/sh\n""$cmd""\n"
    fi

Działanie procesu

Nie wiemy kiedy router zostanie włączony a chcielibyśmy pobrać z niego cyklicznie logi systemowe. Wpisujemy więc dla danego mac adresu polecenie do wykonania (konkretnie logread). Router, kiedy zostanie włączony, co 10min będzie wykonywał skrypt. Przy pierwszym uruchomieniu żadnego polecenia wcześniej jeszcze nie wykonał więc tylko wywoła pobranie danego adresu serwera przekazując mu swój mac adres jako parametr w URL. Serwer zapisze te dane i odeśle skrypt do wykonania (będzie to wspomniany logread). Router odbierze skrypt, wykona go i rezultat zostanie zapisany do pliku /tmp/output.txt na routerze. Po kolejnych 10 minutach router zobaczy, że istnieje rezultat wykonania poprzedniego polecenia, więc zakoduje go w base64, dołączy jako parametr do adresu URL i ponownie pobierze dane z serwera. Cały cykl zostanie powtórzony.

Uwagi końcowe

Podane rozwiązanie jest bardzo proste - nie zawiera mechanizmów autoryzacji, kodowania danych i podobnych elementów. W ten sposób można wykonać dowolne polecenie systemowe co pozwoli na zbadanie np. poziomu sygnału modemu GSM, przesłanie logów, ale także można nakazać np. zestawić tunel ssh do innego hosta czy zdalnie zrobić upgrade systemu.