Lokalne testowanie urządzenia IoT
February 22, 2024

O ile "internet" nie jest pierwszym o czym myślimy słysząc o embedded, to dziś, w czasach Internet of Things, takie skojarzenie jest jak najbardziej na miejscu. Co raz więcej urządzeń łączy się obecnie z siecią, przesyłając dane na serwery i wymieniając je z innymi urządzeniami. Z tego względu wiedza na temat działania sieci komputerowych jest jest w tej branży bardzo przydatna.

Praca z urządzeniami łączącymi się z siecią, często bezprzewodowo poprzez GSM, wiąże się z dodatkowymi utrudnieniami w testowaniu takich urządzeń. W tym artykule skupimy się na konkretnym problemie, który możemy napotkać przy pracy z takimi urządzeniami.

Problem

Załóżmy że pracujemy nad oprogramowaniem urządzenia X. W trakcie pracy nasze urządzenie łączy się z urządzeniem Y po TCP/IP np. aby przesyłać dane pomiarowe, albo odbierać zdalne komendy.
Sytuację przedstawia poniższy rysunek:

Połączenie TCP client - server

Server Y dostępny jest publicznie pod adresem a.b.c.d:e, jednakże my chcielibyśmy mieć możliwość przetestowania komunikacji na naszym komputerze. W takich warunkach jesteśmy w stanie łatwo podejrzeć przebieg komunikacji i naprawić ewentualne błędy. Jeśli mamy dostęp do oprogramowania serwera Y moglibyśmy wystartować go lokalnie na naszym komputerze, jeśli nie - napisać uproszczoną wersję serwera Y, która pozwoli przetestować konkretne scenariusze komunikacji. W obydwu przypadkach pojawia się ten sam problem - jak połączyć nasze urządzenie X z naszym komputerem?

Proxy

Nasze urządzenie X potrafi połączyć się jedynie z adresami IP dostępnymi publicznie. Nasz komputer jednak (zazwyczaj) takiego nie posiada - najprawdopodobniej znajduje się za jednym (lub więcej) routerem który podmienia adresy pakietów (NAT - Network Address Translation). W takiej sytuacji aby móc połączyć urządzenie X z naszym komputerem musimy posłużyć się jakimś serwerem posiadającym publiczny adres IP. Być może posiadamy takowy (my lub nasz pracodawca), jeśli nie to możemy np. względnie tanio wykupić tzw. wirtualny serwer prywatny (VPS - Virtual Private Server).
W dalszej części artykułu zakładamy że, posiadamy dostęp SSH do takiego serwera z systemem Linux.

Zarys rozwiązania

Terminologia:

  • DEVICE - nasze urządzenie które chce się połączyć do aplikacji serwera, my nie możemy się połączyć do niego (nie znamy adresu IP);
  • PC - nasz komputer, nie znamy jego adresu IP, lecz chcemy aby DEVICE się do niego połączyło;
  • PROXY - serwer z publicznym IP do którego mamy dostęp;

Aby umożliwić połączenie musimy wykorzystać PROXY jako pośrednika pomiędzy DEVICE a PC, co ilustruje poniższy rysunek:

Kaskada połączeń: IoT device -> Proxy -> PC

Na urządzeniu PROXY będziemy nasłuchiwać na połączenia ze strony DEVICE. Gdy DEVICE się połączy, całość komunikacji przekierujemy na nasz komputer PC. Aby umożliwić takie przekierowanie będziemy musieli przygotować tzw. "reverse proxy" z naszego PC. Całość możemy wykonać przy pomocy kilku standardowych programów dostępnych w systemie Linux.

Zanim przejdziemy do ostatecznego rozwiązania musimy przedstawić dostępne narzędzia i zarysować kilka problemów.

Netcat

Netcat (nc) to proste narzędzie do przesyłania danych po TCP/UDP. Często jest to pierwszy wybór przy testowaniu/debugowaniu połączeń. Netcat pozwala na nasłuchiwanie/łączenie się po sieci oraz przesyła dane ze strumieni STDIN/STDOUT, przez co łatwo komponuje się z innymi narzędziami Unix-owymi.
Dwa typowe rodzaje wywołań to:
  • Serwer TCP nasłuchujący na danym porcie, np. nc -l -p 8000 nasłuchuje na porcie 8000 localhosta i po połączeniu się klienta wypisuje wszystkie dane na STDOUT oraz przesyła do klienta dane które wpiszemy na STDIN.
  • Klient TCP łączący się pod dany adres, np. nc 42.42.42.42 8000 połączy się z adresem 42.42.42.42 na porcie 8000 i, podobnie jak poprzednio, prześle dane otrzymane na STDIN, wyświetlając na STDOUT dane otrzymane od serwera.

Żeby zobaczyć jak można wykorzystać to narzędzie możemy wykonać następujące czynności:

  • Terminal 1: wywołujemy nc -l -p 8123
  • Terminal 2: wywołujemy nc localhost 8123 < README.md

Zakładając że plik README.md istnieje w obecnym katalogu terminala 2, zostanie on przesłany do serwera i wyświetlony w Terminalu 1.

Reverse proxy

Wracając do naszego oryginalnego problemu, brakuje nam wciąż sposobu na połączenie z PROXY do naszego PC - możemy jedynie nawiązywać połączenia z PC do PROXY (ponieważ tylko PROXY ma publicznie widoczny adres IP). Aby umożliwić połączenia z PROXY do PC musimy utworzyć tzw. "reverse proxy". Polega to na połączeniu się z PC do PROXY (to umiemy), po czym przekierowywanie połączeń z PROXY do PC "wewnątrz" wcześniej utworzonego połączenia (tzw. tunelowanie).

Utworzenie reverse proxy jest możliwe za pomocą SSH, wystarczy skorzystać z argumentu -R.

Przykład wywołania:

ssh -N -R 8200:42.42.42.42:8300 user@server

Składnia na początku może być trudna do rozczytania, więc przeanalizujmy po kolei argumenty wywołania:

  • ssh user@server - standardowe połączenie po SSH do serwera pod adresem/domeną server jako użytkownik user (np. z adresem IP ssh myuser@42.42.42.42 lub domeną ssh myuser@my.server.com). Czasami może być konieczne podanie portu innego niż domyślny 22, wtedy używamy -p (np. -p 2222 );
  • -N - nie startuj terminala (nie potrzebujemy go w tym przypadku);
  • -R - utwórz reverse proxy;
    • 8200 - port lokalny na serwerze server z którego połączenia będą tunelowane;
    • 42.42.42.42:8300 - adres (widziany z perspektywy komputera na którym wywołujemy komendę) do którego połączenia będą tunelowane;

Sytuację tą ilustruje poniższy rysunek:

Reverse proxy

W naszym przypadku, gdy nasz lokalny serwer startowany jest na naszym PC, tunelowanie możemy wykonywać do localhosta (127.0.0.1), czyli np. gdy lokalny serwer startujemy na porcie 8300:

ssh -N -R 8200:127.0.0.1:8300 user@PROXY

Przekierowanie lokalne na PROXY

O ile wcześniej przedstawione reverse proxy pozwala przekierować połączenia z PROXY na porcie 8200 do naszego PC, to SSH będzie nasłuchiwało jedynie połączeń lokalnych na port 8200, a więc połączenia z naszego urządzenia DEVICE do PROXY na port 8200 nie będą akceptowane. Można to zmienić w ustawieniach demona SSH (sshd), ale zazwyczaj nie chcemy tego robić. Zamiast tego możemy utworzyć dodatkowe przekierowanie z pomocą Netcata.

Zakładając że nasze reverse proxy nasłuchuje na PROXY lokalnych połączeń na porcie 8200, możemy utworzyć lokalne przekierowanie (już na urządzeniu PROXY) z portu 8100 na port 8200, przy czym na porcie 8100 będziemy nasłuchiwać również połączeń zdalnych (spoza urządzenia PROXY). Aby to zrobić możemy skorzystać z Netcata w następujący sposób:

nc -k -l 8100 -c 'nc 127.0.0.1 8200'

Gdzie:

  • -k sprawia że Netcat będzie czekał również na kolejne połączenia gdy pierwsze się rozłączy, zamiast przerwać działanie po pierwszym połączeniu;
  • -l 8100 nasłuchuje na wszystkich połączeń (również zdalnych) na porcie 8100
  • -c po połączeniu klienta Netcat wywoła podaną komendę i przekieruje do niej połączenie (łączy STDIN/STDOUT);
  • nc 127.0.0.1 8200 łączy z localhostem na porcie 8200, czyli tam gdzie nasłuchuje nasze reverse proxy
Należy zwrócić uwagę na dostępność flagi -c w naszej implementacji Netcata, ponieważ nie zawsze jest wspierana (Ncat wspiera tą flagę). Wykorzystanie Unix-owymi strumieni ("pipe"), np. nc -k -l 8100 | nc 127.0.0.1 8200, nie sprawdzi się tutaj, ponieważ połączenie do 127.0.0.1 8200 zostałoby nawiązane tylko raz, i to od razu przy wywołaniu komendy, a nie dopiero gdy ktoś połączy się na porcie 8100.

Socat

Choć wygodny do szybkich testów, Netcat ma swoje ograniczenia, a dostępne funkcje różnią się pomiędzy implementacjami. Znacznie potężniejszą alternatywą jest narzędzie socat , które pozwala na zastąpienie funkcjonalności Netcata, jak i na wiele więcej.Aby zastąpić nasze poprzednie wywołanie nc -k -l 8100 -c 'nc 127.0.0.1 8200' (i uniezależnić się od konkretnej implementacji Netcata), możemy skorzystać z Socata w następujący sposób:
socat tcp-listen:8100,reuseaddr,fork tcp:localhost:8200
Gdzie:
  • tcp-listen:8100 - nasłuchiwanie TCP na porcie 8100, jak nc -l 8100
  • reuseaddr,fork - odpowiada za ciągłe nasłuchiwanie, podobnie do nc -k
  • tcp:localhost:8200 - łączy z localhostem (127.0.0.1) na porcie 8200, zastępuje -c 'nc 127.0.0.1 8200'

Firewal

Zanim przedstawimy ostateczne rozwiązanie, warto wspomnieć o ustawieniach naszego PROXY. Możliwe że domyślnie nasz serwer posiada skonfigurowaną "zaporę ogniową" uniemożliwiającą połączenia na większości portów. Jeśli tak jest musimy dodać odpowiednie wyjątki do konfiguracji zapory.

W systemie Linux można to zrobić przy pomocy komendy iptables konfigurującej zaporę wbudowaną w jądro systemu Linux. Przykładowo, aby dodać wyjątek umożliwiający połączenia przy pomocy protokołu TCP na porcie 9000 możemy użyć poniższej komendy:
sudo iptables -I INPUT -p tcp --dport 9000 -j ACCEPT

Konfiguracja firewall może być różna zależnie od konfiguracji naszego serwera PROXY i ciężko jest tu przedstawić konkretne rozwiązanie.

Dokładne wytłumaczenie działania iptables zajęłoby zbyt wiele czasu aby zmieścić się w tym artykule, więcej informacji o tej komendzie znajdziecie w Referencjach do artykułu.

Ostateczna komenda

Posiadamy już wszystkie potrzebne elementy układanki:

  • startujemy nasz serwer do testów na PC na porcie 8300
  • na PC startujemy reverse proxy z PROXY do naszego lokalnego serwera za pomocą ssh -N -R 8200:127.0.0.1:8300 user@PROXY
  • łączymy się do PROXY i wywołujemy tam socat tcp-listen:8100,reuseaddr,fork tcp:localhost:8200

Teraz każdorazowo gdy DEVICE połączy się z PROXY na porcie 8100, połączenie zostanie przekierowane do naszego lokalnego serwera testowego na PC na porcie 8300, a my możemy testować komunikację. Poniższy rysunek ilustruje ostateczną konfigurację:

Finalne połączenie

Wszystko to możemy zawrzeć w jednej komendzie, aby łatwiej startować nasze przekierowanie DEVICE->PROXY->PC:

ssh -t -R 8200:127.0.0.1:8300 user@PROXY 'socat tcp-listen:8100,reuseaddr,fork tcp:localhost:8200'

Gdzie:

  • ssh user@PROXY - łączymy się SSH z PROXY
  • -R 8200:127.0.0.1:8300 - tworzymy reverse proxy
  • socat tcp-listen:8100,reuseaddr,fork tcp:localhost:8200 - po połączeniu SSH startujemy komendę socat z lokalnym przekierowaniem (wywoływane już na serwerze PROXY)
  • -t - alokuje PTY, po to, aby po przerwaniu połączenia SSH (wciśnięcie Ctrl+D / zamknięcie terminala), przerwało również wywołanie komendy socat

Tak ustanowione przekierowanie możemy przetestować na PC, bez udziału DEVICE i bez startowania naszego lokalnego serwera testowego, za pomocą Netcata:

  • Terminal 1: nc -l -p 8300 | hexdump -C - imituje nasz lokalny serwer testowy nasłuchujący na porcie 8300, wypisze hexdump otrzymanych danych
  • Terminal 2: nc PROXY 8100 < README.md, gdzie PROXY to adres naszego serwera PROXY - imituje dane wysyłane przez DEVICE do PROXY na port 8100

Tak wysłane dane z pliku README.md przejdą całą drogę z Netcata na PC do PROXY:8100, poprzez lokalne przekierowanie na PROXY:8200, i poprzez SSH reverse proxy na PC:8300 do nasłuchującego tam Netcata z hexdumpem.

Referencje: