Jak zacząć korzystać z trybu użytkownika w systemie Linux

Wstęp od tłumacza: W dobie masowego wprowadzenia różnego rodzaju kontenerów do naszego życia, warto poznać technologie, od których to wszystko się zaczynało. Niektóre z nich można wciąż z powodzeniem stosować, ale nie wszyscy pamiętają o tych metodach (lub nie wiedzą, jeśli nie byli świadkami ich dynamicznego rozwoju). Jedną z takich technologii jest User Mode Linux. Autorka oryginału dokładnie zbadała, co z dawnych rozwiązań wciąż działa, a co już nie, i przygotowała coś w rodzaju szczegółowej instrukcji, jak samodzielnie uruchomić domowy UML w 2019 roku. Tak, zaprosiliśmy do Habr autorkę pierwotnego posta. Cadey, więc jeśli macie pytania — pytajcie po angielsku w komentarzach.

Jak zacząć korzystać z trybu użytkownika w systemie Linux

User Mode w Linuxie to w zasadzie port jądra Linux na samego siebie. Tryb ten pozwala na uruchomienie pełnoprawnego jądra Linux jako procesu użytkownika i zazwyczaj jest stosowany przez deweloperów do testowania sterowników. Ale ten tryb przydaje się także jako narzędzie ogólnej izolacji, której zasady są podobne do działania maszyn wirtualnych. Zapewnia on większą izolację niż Docker, ale mniejszą niż pełnoprawna maszyna wirtualna, taka jak KVM czy Virtual Box.

Ogólnie rzecz biorąc, User Mode może wydawać się dziwnym i skomplikowanym narzędziem, ale ma swoje obszary zastosowań. To pełnoprawne jądro Linux działające jako użytkownik bez uprawnień. Ta cecha pozwala na uruchamianie potencjalnie niebezpiecznego kodu bez zagrożeń dla maszyny hosta. A ponieważ jest to pełnoprawne jądro, jego procesy są izolowane od maszyny hosta, co oznacza, że procesy działające wewnątrz User Mode nie będą widoczne dla hosta. To nie jest podobne do znanego kontenera Docker, w przypadku którego maszyna hosta zawsze widzi procesy wewnątrz repozytorium. Spójrz na ten fragment pstree z jednego z moich serwerów:

containerd─┬─containerd-shim─┬─tini─┬─dnsd───19*[{dnsd}]
           │                 │      └─s6-svscan───s6-supervise
           │                 └─10*[{containerd-shim}]
           ├─containerd-shim─┬─tini─┬─aerial───21*[{aerial}]
           │                 │      └─s6-svscan───s6-supervise
           │                 └─10*[{containerd-shim}]
           ├─containerd-shim─┬─tini─┬─s6-svscan───s6-supervise
           │                 │      └─surl
           │                 └─9*[{containerd-shim}]
           ├─containerd-shim─┬─tini─┬─h───13*[{h}]
           │                 │      └─s6-svscan───s6-supervise
           │                 └─10*[{containerd-shim}]
           ├─containerd-shim─┬─goproxy───14*[{goproxy}]
           │                 └─9*[{containerd-shim}]
           └─32*[{containerd}]

Porównaj to z pstree jądra Linux w trybie użytkownika:

linux─┬─5*[linux]
      └─slirp

Pracując z kontenerami Docker, mogę zobaczyć z hosta nazwy procesów uruchomionych w systemie gościa. To jest niemożliwe w trybie użytkownika Linux. Co to oznacza? Oznacza to, że narzędzia do monitorowania, działające przez podsystem audytu Linux (Linux’s auditing subsystem) nie widzą procesów uruchamianych w systemie gościa. Ale w pewnych sytuacjach ta cecha może stać się mieczem obosiecznym.

Właściwie cały poniższy post to zestaw badań i prób osiągnięcia zamierzonego rezultatu. Do tego musiałem korzystać z różnych starych narzędzi, czytać źródła jądra, intensywnie debugować kod, który był napisany w czasach, kiedy jeszcze chodziłem do podstawówki, a także grzebać w kompilacjach Heroku za pomocą specjalnego pliku binarnego w poszukiwaniu potrzebnych mi narzędzi. Cała ta praca doprowadziła do tego, że ludzie w moim IRC zaczęli nazywać mnie czarodziejką (magic). Mam nadzieję, że ten post będzie dla kogoś solidną dokumentacją, aby powtórzyć to samo, ale już z nowszymi jądrami i wersjami OS.

Konfiguracja

Ustawienie trybu użytkownika Linux przebiega w kilku etapach:

  • instalacja zależności na hoście;
  • pobranie jądra Linux;
  • konfiguracja kompilacji jądra;
  • kompilacja jądra;
  • instalacja pliku binarnego;
  • konfiguracja systemu plików gościa;
  • dobór parametrów uruchomienia jądra;
  • konfiguracja sieci gościa;
  • uruchomienie gościnnego jądra.

Zakładam, że jeśli zdecydujecie się na samodzielne przeprowadzenie tego wszystkiego, najprawdopodobniej będziecie działać w systemie podobnym do Ubuntu lub Debian. Próbowałam zrealizować wszystko, co opisano powyżej, w mojej ulubionej dystrybucji – Alpine, ale nic z tego nie wyszło, najwidoczniej z powodu tego, że jądro Linux ma ścisłą więź z glibc-izmami dla sterowników w trybie użytkownika. Planuję zgłosić to do upstreamu po tym, jak ostatecznie rozwiążę problem.

Instalacja zależności na hoście

Ubuntu wymaga przynajmniej następujących pakietów do kompilacji jądra Linux (zakładając czystą instalację):

- 'build-essential'
- 'flex'
- 'bison'
- 'xz-utils'
- 'wget'
- 'ca-certificates'
- 'bc'
- 'linux-headers'

Możesz je zainstalować za pomocą następującej komendy (z uprawnieniami roota lub używając sudo):

apt-get -y install build-essential flex bison xz-utils wget ca-certificates bc 
                   linux-headers-$(uname -r)

Zauważ, że uruchomienie programu konfiguracji menu dla jądra Linux wymaga zainstalowania libncurses-dev. Proszę upewnij się, że jest on zainstalowany za pomocą następującej komendy (z uprawnieniami roota lub używając sudo):

apt-get -y install libncurses-dev

Pobieranie jądra

Określ lokalizację do pobrania i późniejszej kompilacji jądra. Ta operacja będzie wymagać około 1,3 GB miejsca na dysku twardym, więc upewnij się, że je masz.

Następnie przejdź do kernel.org i uzyskaj URL do pobrania najnowszej stabilnej wersji jądra. W momencie pisania tego posta jest to: https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.1.16.tar.xz

Pobierz ten plik za pomocą 'wget':

wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.1.16.tar.xz

I rozpakuj go za pomocą 'tar':

tar xJf linux-5.1.16.tar.xz

Teraz wchodzimy do katalogu utworzonego podczas rozpakowywania tarballa:

cd linux-5.1.16

Konfiguracja kompilacji jądra

System kompilacji jądra to zestaw plików Make z z wieloma narzędziami i skryptami użytkownika do automatyzacji procesu. Na początek otwórz interaktywny program konfiguracji:

make ARCH=um menuconfig

Częściowo przeprowadzi kompilację i wyświetli okno dialogowe. Gdy na dole okna pojawi się ‘[Select]‘, będziesz mógł zająć się konfiguracją przy użyciu klawiszy Spacja lub Enter. Nawigacja po oknie, jak zwykle, za pomocą strzałek na klawiaturze „w górę” i „w dół”, a wybieranie elementów – „w lewo” lub „w prawo”.

Wskaźnik „->” oznacza, że znajdujesz się w podmenu, do którego wejście odbywa się klawiszem Enter. Wyjście z niego, oczywiście, poprzez ‘[Exit]‘.

Włącz następujące opcje w ‘[Select]‘ i upewnij się, że obok nich znajduje się symbol ‘[*]’:

Opcje specyficzne dla UML:
  - System plików hosta
Wsparcie dla sieci (włącz to, aby pokazać podmenu):
  - Opcje sieciowe:
    - Sieć TCP/IP
Urządzenia sieciowe UML:
  - Wirtualne urządzenie sieciowe
  - Transport SLiRP

Wszystko, z tego okna możesz wyjść, wybierając kolejno „[Exit]“. Tylko upewnij się, że na końcu zaproponuje zapisanie konfiguracji i wybierzesz „[Tak]‘.

Zalecam, abyś pobawił się opcjami kompilacji jądra po przeczytaniu tego wpisu. Dzięki tym eksperymentom wiele zyskasz w kwestii zrozumienia mechanizmów jądra niskiego poziomu oraz wpływu różnych flag na jego kompilację.

Kompilacja jądra

Jądro Linux to duży program zajmujący się wieloma rzeczami. Nawet przy tak minimalnej konfiguracji na starym sprzęcie jego kompilacja może zająć dużo czasu. Dlatego skompiluj jądro za pomocą następującego polecenia:

make ARCH=um -j$(nproc)

Dlaczego? To polecenie powie naszemu kompilatorowi, aby użył wszystkich dostępnych rdzeni i wątków procesora podczas kompilacji. Polecenie $(nproc) na końcu kompilacji podstawia wynik polecenia nproc, które jest częścią coreutils w standardowej kompilacji Ubuntu.

Po pewnym czasie nasze jądro zostanie skompilowane do pliku wykonywalnego ./linux.

Instalacja binarki

Ponieważ User Mode w Linuxie tworzy zwykłą binarkę, możesz ją zainstalować jak każdą inną aplikację. Oto jak to zrobiłem:

mkdir -p ~/bin
cp linux ~/bin/linux

Warto również upewnić się, że ~/bin znajduje się w twoim $PATH:

export PATH=$PATH:$HOME/bin

Konfiguracja systemu plików gościa

Utwórz katalog dla systemu plików gościa:

mkdir -p $HOME/prefix/uml-demo
cd $HOME/prefix

Odwiedź alpinelinux.org i w dziale pobierania znajdź aktualny link do pobrania MINI ROOT FILESYSTEM. W momencie pisania tej publikacji było to:

http://dl-cdn.alpinelinux.org/alpine/v3.10/releases/x86_64/alpine-minirootfs-3.10.0-x86_64.tar.gz

Pobierz ten tarball, korzystając z wget:

wget -O alpine-rootfs.tgz http://dl-cdn.alpinelinux.org/alpine/v3.10/releases/x86_64/alpine-minirootfs-3.10.0-x86_64.tar.gz

Teraz wejdź do katalogu systemu plików gościa i rozpakuj archiwum:

cd uml-demo
tar xf ../alpine-rootfs.tgz

Opisane działania stworzą mały szablon systemu plików. Z powodu specyfiki działania systemu instalacja pakietów przez menedżera apk Alpine będzie niezwykle trudna. Jednak ten system plików wystarczy, aby ocenić ogólną ideę.

Będziemy potrzebować również narzędzia tini do ograniczenia zużycia pamięci przez procesy zombie naszego jądra gościa.

wget -O tini https://github.com/krallin/tini/releases/download/v0.18.0/tini-static
chmod +x tini

Tworzenie wiersza poleceń jądra

W jądrze Linux, jak w większości innych programów, znajdują się argumenty linii poleceń, z którymi można się zapoznać, podając klucz --help.

Sam —help

linux --help
User Mode Linux v5.1.16
        dostępny pod http://user-mode-linux.sourceforge.net/

--showconfig
    Wyświetla plik konfiguracyjny, z którego ten binarny UML został wygenerowany.

iomem=,
    Konfiguruje  jako region pamięci IO o nazwie .

mem=
    Kontroluje, ile "fizycznej" pamięci jądro przydziela
    dla systemu. Rozmiar określa się jako liczba, za którą
    następuje jedno z 'k', 'K', 'm', 'M', co ma oczywiste znaczenie.
    To nie jest związane z ilością pamięci w hoście. Może
    być większa, a nadmiar, jeśli kiedykolwiek zostanie użyty, po prostu zostanie wymieniony.
        Przykład: mem=64M

--help
    Wyświetla tę wiadomość.

debug
    ta flaga nie jest potrzebna do uruchomienia gdb na UML w trybie skas

root=
    Jest to właściwie używane przez ogólne jądro dokładnie w ten sam
    sposób, jak w każdym innym jądrze. Jeśli skonfigurujesz liczne urządzenia blokowe
    i chcesz uruchomić z czegoś innego niż ubd0, musiałbyś użyć czegoś takiego jak:
        root=/dev/ubd5

--version
    Wyświetla numer wersji jądra.

umid=
    Używane do przypisania unikalnej tożsamości tej maszyny UML i
    jest używane do nazywania pliku pid i gniazda konsoli zarządzającej.

con[0-9]*=
    Podłącz konsolę lub linię szeregową do kanału hosta. Zobacz
    http://user-mode-linux.sourceforge.net/old/input.html po pełen
    opis tego przełącznika.

eth[0-9]+=,
    Konfiguracja urządzenia sieciowego.
    
aio=2.4
    Używane do wymuszenia, aby UML używał 2.4 stylu AIO, nawet gdy 2.6 AIO jest
    dostępne. 2.4 AIO to jeden wątek, który obsługuje jedno żądanie na raz,
    synchronicznie. 2.6 AIO to wątek, który używa interfejsu 2.6 AIO
    do obsługi dowolnej liczby oczekujących żądań. 2.6 AIO
    nie jest dostępne w trybie tt, na hostach 2.4, ani gdy UML jest zbudowane z
    /usr/include/linux/aio_abi.h nie jest dostępne. Wiele dystrybucji nie zawiera
    aio_abi.h, więc musisz skopiować go z drzewa jądra do
    swojego /usr/include/linux, aby zbudować UML zdolny do AIO

nosysemu
    Wyłącza emulację wywołań systemowych dla ptrace (SYSEMU).
    SYSEMU to poprawka wydajności wprowadzona przez Laurent Vivier. Zmienia
    zachowanie ptrace() i pomaga zmniejszyć tempo zmian kontekstu hosta.
    Aby to działało, potrzebujesz poprawki jądra dla swojego hosta również.
    Zobacz http://perso.wanadoo.fr/laurent.vivier/UML/ po dalsze
    informacje.

uml_dir=
    Lokalizacja do umieszczania plików pid i umid.

quiet
    Wyłącza komunikaty informacyjne podczas uruchamiania.

hostfs=,,...
    Używane do ustawiania parametrów hostfs. Argument katalogu root
    jest używany do ograniczenia wszystkich montażów hostfs w określonym drzewie katalogów
    na hoście. Jeśli nie jest to określone, to użytkownik wewnątrz UML może
    zamontować cokolwiek na hoście, co jest dostępne dla użytkownika, który go uruchamia.
    Obecnie jedyną obsługiwaną flagą jest 'append', która określa, że wszystkie
    pliki otwierane przez hostfs będą otwierane w trybie dołączania.

Ten banner przedstawia podstawowe parametry uruchamiania. Uruchommy jądro z minimalnym zestawem opcji:

linux 
  root=/dev/root 
  rootfstype=hostfs 
  rootflags=$HOME/prefix/uml-demo 
  rw 
  mem=64M 
  init=/bin/sh

Powyższe wiersze mówią naszemu jądru, co ma zrobić:

  • Załóżmy, że system plików root jest pseudo-urządzeniem /dev/root.
  • Wybierz hostfs jako sterownik głównego systemu plików.
  • Zamontuj gościnny system plików, który stworzyliśmy w urządzeniu root.
  • Tak, w trybie odczytu-zapisu.
  • Użyj tylko 64 megabajtów pamięci RAM (możesz użyć znacznie mniej, w zależności od tego, co zamierzasz robić, ale 64 MB wydają się optymalną wielkością).
  • Jądro uruchamia automatycznie /bin/sh jak init-proces.

Uruchom tę komendę, a powinieneś otrzymać coś podobnego do poniższego:

Jeszcze jeden arkusz

Limity zrzutu pamięci:
        miękki - 0
        twardy - BRAK
Sprawdzanie, czy ptrace może zmieniać numery wywołań systemowych...OK
Sprawdzanie poprawki emulacji wywołań systemowych dla ptrace...OK
Sprawdzanie zaawansowanej poprawki emulacji wywołań systemowych dla ptrace...OK
Sprawdzanie zmiennych środowiskowych dla katalogu tymczasowego...nie znaleziono
Sprawdzanie, czy /dev/shm jest na tmpfs...OK
Sprawdzanie PROT_EXEC mmap w /dev/shm...OK
Dodawanie 32137216 bajtów do pamięci fizycznej, aby uwzględnić lukę w exec-shield
Wersja jądra Linux 5.1.16 (cadey@kahless) (gcc wersja 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) #30 Ndz lip 7 18:57:19 UTC 2019
Zbudowano 1 listy stref, grupowanie mobilności włączone.  Całkowita liczba stron: 23898
Linia poleceń jądra: root=/dev/root rootflags=/home/cadey/dl/uml/alpine rootfstype=hostfs rw mem=64M init=/bin/sh
Wpisy w tabeli pamięci podręcznej dentry: 16384 (zamówienie: 5, 131072 bajtów)
Wpisy w tabeli pamięci podręcznej inode: 8192 (zamówienie: 4, 65536 bajtów)
Pamięć: 59584K/96920K dostępnych (2692K kodu jądra, 708K rwdata, 588K rodata, 104K init, 244K bss, 37336K zarezerwowane, 0K zarezerwowane cma)
SLUB: HWalign=64, Zamówienie=0-3, MinObjects=0, CPU=1, Węzły=1
NR_IRQS: 15
źródło zegara: timer: mask: 0xffffffffffffffff max_cycles: 0x1cd42e205, max_idle_ns: 881590404426 ns
Kalibrowanie pętli opóźnienia... 7479.29 BogoMIPS (lpj=37396480)
pid_max: domyślne: 32768 minimum: 301
Wpisy w tabeli pamięci podręcznej zamontowanych katalogów: 512 (zamówienie: 0, 4096 bajtów)
Wpisy w tabeli pamięci podręcznej punktów montażu: 512 (zamówienie: 0, 4096 bajtów)
Sprawdzanie, czy ptys w hoście obsługują sygnał SIGIO...Tak
Sprawdzanie, czy ptys w hoście obsługują SIGIO przy zamknięciu...Nie, włączam obejście
devtmpfs: zainicjowany
random: get_random_bytes wywołane z setup_net+0x48/0x1e0 z crng_init=0
Używanie 2.6 host AIO
źródło zegara: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns
Wpisy w tabeli hash dla futexów: 256 (zamówienie: 0, 6144 bajtów)
NET: Zarejestrowano rodzinę protokołów 16
źródło zegara: Przełączono na źródło zegara timer
NET: Zarejestrowano rodzinę protokołów 2
tcp_listen_portaddr_hash wpisy w tabeli hash: 256 (zamówienie: 0, 4096 bajtów)
TCP zarejestrowane wpisy w tabeli hash: 1024 (zamówienie: 1, 8192 bajtów)
TCP bind wpisy w tabeli hash: 1024 (zamówienie: 1, 8192 bajtów)
TCP: Tabele hash skonfigurowane (ustalony 1024 bind 1024)
UDP wpisy w tabeli hash: 256 (zamówienie: 1, 8192 bajtów)
UDP-Lite wpisy w tabeli hash: 256 (zamówienie: 1, 8192 bajtów)
NET: Zarejestrowano rodzinę protokołów 1
konsola [stderr0] wyłączona
mconsole (wersja 2) zainicjowana w /home/cadey/.uml/tEwIjm/mconsole
Sprawdzanie, czy host MADV_REMOVE obsługuje...OK
workingset: timestamp_bits=62 max_order=14 bucket_order=0
Warstwa blokowa SCSI ogólnego zastosowania (bsg) sterownik wersja 0.4 załadowany (major 254)
scheduler wejścia-wyjścia noop zarejestrowany (domyślny)
scheduler wejścia-wyjścia bfq zarejestrowany
loop: moduł załadowany
NET: Zarejestrowana rodzina protokołów 17
Zainicjowano sterownik konsoli stdio
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 1: Konfiguracja nie powiodła się
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 2: Konfiguracja nie powiodła się
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 3: Konfiguracja nie powiodła się
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 4: Konfiguracja nie powiodła się
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 5: Konfiguracja nie powiodła się
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 6: Konfiguracja nie powiodła się
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 7: Konfiguracja nie powiodła się
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 8: Konfiguracja nie powiodła się
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 9: Konfiguracja nie powiodła się
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 10: Konfiguracja nie powiodła się
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 11: Konfiguracja nie powiodła się
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 12: Konfiguracja nie powiodła się
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 13: Konfiguracja nie powiodła się
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 14: Konfiguracja nie powiodła się
Używanie typu kanału, który jest skonfigurowany poza UML
setup_one_line nie powiódł się dla urządzenia 15: Konfiguracja nie powiodła się
Konsola zainicjowana w /dev/tty0
konsola [tty0] włączona
konsola [mc-1] włączona
Nie udało się zainicjować urządzenia ubd 0: Nie można określić rozmiaru pliku urządzenia
VFS: Zamontowano root (system plików hostfs) na urządzeniu 0:11.
devtmpfs: zamontowane
Ta architektura nie ma ochrony pamięci jądra.
Uruchom /bin/sh jako proces init
/bin/sh: nie można uzyskać dostępu do tty; kontrola zadania wyłączona
random: szybkie inicjowanie zakończone
/ # 

Powyższe działania przyniosą nam minimalny system gościnny, bez takich rzeczy jak /proc czy przypisany hostname. Na przykład, spróbuj wykonać następujące polecenia:

- uname -av
- cat /proc/self/pid
- hostname

Aby wyjść z systemu gościnnego, wpisz exit lub naciśnij control-d. To zakończy powłokę z następującym kernel panic:

/ # exit
Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000000
fish: “./linux root=/dev/root rootflag…” terminated by signal SIGABRT (Abort)

Otrzymaliśmy ten kernel panic, ponieważ jądro Linuxa uważa, że proces inicjalizacji zawsze działa. Bez niego system nie może już funkcjonować i zamyka się. Jednakże, ponieważ jest to proces w trybie użytkownika, uzyskany wynik wysyła siebie do SIGABRT, co prowadzi do wyjścia.

Konfiguracja sieci gościnnej

A tutaj zaczynamy mieć problemy. Sieć w User Mode Linux to miejsce, gdzie cała koncepcja ograniczonego "trybu użytkownika" zaczyna się rozpadać. Zazwyczaj na poziomie systemowym sieć jest ograniczona przywilejami trybów wykonywania, ze znanych nam powodów.

Przyp. tłum.: więcej o różnych opcjach pracy z siecią w UML można poczytać tutaj.

Podróż do slirp

Jednak istnieje stary i praktycznie nie wspierany program o nazwie Slirp, który pozwala User Mode Linux na interakcję z siecią. Działa on w zasadzie jak stos TCP/IP na poziomie użytkownika i nie wymaga żadnych uprawnień systemowych do uruchomienia. Narzędzie to zostało wydane w 1995 roku, a ostatnia aktualizacja miała miejsce w 2006 roku. Slirp jest bardzo stary. Przez czas braku wsparcia i aktualizacji kompilatory poszły tak daleko, że to narzędzie można scharakteryzować tylko jako "code rot".

Więc, zainstalujmy Slirp z repozytoriów Ubuntu i spróbujmy go uruchomić:

sudo apt-get install slirp
/usr/bin/slirp
Slirp v1.0.17 (BETA)

Copyright (c) 1995,1996 Danny Gasparovski i inni.
Wszystkie prawa zastrzeżone.
Ten program jest objęty prawami autorskimi, jest oprogramowaniem typu open source.
Proszę zapoznać się z plikiem COPYRIGHT, który został dołączony do pakietu Slirp
w celu uzyskania warunków ochrony praw autorskich.

Adres IP hosta Slirp: 127.0.0.1
Adres(y) IP twojego DNS: 1.1.1.1, 10.77.0.7
Twój adres to 10.0.2.15
(lub cokolwiek innego chcesz)

Wpisz pięć zer (0), aby wyjść.

[autodetect SLIP/CSLIP, MTU 1500, MRU 1500, 115200 baud]

SLiRP Gotowy ...
fish: "/usr/bin/slirp" zakończony sygnałem SIGSEGV (Błąd granicy adresu)

Ojej. Zainstalujmy debugger dla Slirp i zobaczmy, czy możemy rozwiązać ten problem:

sudo apt-get install gdb slirp-dbgsym
gdb /usr/bin/slirp
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /usr/bin/slirp...Reading symbols from /usr/lib/debug/.build-id/c6/2e75b69581a1ad85f72ac32c0d7af913d4861f.debug...done.
done.
(gdb) run
Starting program: /usr/bin/slirp
Slirp v1.0.17 (BETA)

Copyright (c) 1995,1996 Danny Gasparovski and others.
All rights reserved.
This program is copyrighted, free software.
Please read the file COPYRIGHT that came with the Slirp
package for the terms and conditions of the copyright.

IP address of Slirp host: 127.0.0.1
IP address of your DNS(s): 1.1.1.1, 10.77.0.7
Your address is 10.0.2.15
(or anything else you want)

Type five zeroes (0) to exit.

[autodetect SLIP/CSLIP, MTU 1500, MRU 1500, 115200 baud]

SLiRP Ready ...

Program received signal SIGSEGV, Segmentation fault.
                                                    ip_slowtimo () at ip_input.c:457
457     ip_input.c: No such file or directory.

Błąd występuje w tej linii. Sprawdźmy stacktrace, może tam znajdziemy coś pomocnego:

(gdb) bt full
#0  ip_slowtimo () at ip_input.c:457
        fp = 0x55784a40
#1  0x000055555556a57c in main_loop () at ./main.c:980
        so = 
        so_next = 
        timeout = {tv_sec = 0, tv_usec = 0}
        ret = 0
        nfds = 0
        ttyp = 
        ttyp2 = 
        best_time = 
        tmp_time = 
#2  0x000055555555b116 in main (argc=1, argv=0x7fffffffdc58) at ./main.c:95
No locals.

Widzimy, że błąd występuje podczas uruchamiania głównej pętli, gdy slirp próbuje sprawdzić czasy oczekiwania. W tym momencie powinnam zrezygnować z próby debugowania. Ale sprawdźmy, czy Slirp skompilowany z źródeł działa. Ponownie pobrałam archiwum bezpośrednio ze strony Sourceforge, ponieważ pobieranie czegokolwiek z wiersza poleceń to kłopot:

cd ~/dl
wget https://xena.greedo.xeserv.us/files/slirp-1.0.16.tar.gz
tar xf slirp-1.0.16.tar.gz
cd slirp-1.0.16/src
./configure --prefix=$HOME/prefix/slirp
make

Widzimy alerty dotyczące niezdefiniowanych funkcji wbudowanych, co oznacza, że nie można zlinkować powstałego pliku binarnego. Wygląda na to, że w okresie między 2006 rokiem a obecnym momentem gcc przestał tworzyć symbole używane w wbudowanych funkcjach plików skompilowanych pośrednio. Spróbujmy zastąpić słowo kluczowe inline pustym komentarzem i sprawdźmy wynik:

vi slirp.h
:6
a

#define inline /**/

:wq
make

Nie. To także nie działa. Wciąż nie mogę znaleźć symboli tych funkcji.

Na tym etapie się poddałam i zaczęłam szukać na Githubie pakiety kompilacji Heroku. Moja teoria opierała się na tym, że w jakimś pakiecie kompilacji Heroku będą potrzebne mi pliki binarne. Ostatecznie poszukiwania doprowadziły mnie tutaj. Ściągnęłam i rozpakowałam uml.tar.gz i znalazłam następujące:

total 6136
-rwxr-xr-x 1 cadey cadey   79744 Dec 10  2017 ifconfig*
-rwxr-xr-x 1 cadey cadey     373 Dec 13  2017 init*
-rwxr-xr-x 1 cadey cadey  149688 Dec 10  2017 insmod*
-rwxr-xr-x 1 cadey cadey   66600 Dec 10  2017 route*
-rwxr-xr-x 1 cadey cadey  181056 Jun 26  2015 slirp*
-rwxr-xr-x 1 cadey cadey 5786592 Dec 15  2017 uml*
-rwxr-xr-x 1 cadey cadey     211 Dec 13  2017 uml_run*

To jest plik binarny slirp! A czy on działa?

.\/slirp
Slirp v1.0.17 (BETA) FULL_BOLT

Copyright (c) 1995,1996 Danny Gasparovski i inni.
Wszystkie prawa zastrzeżone.
Ten program jest objęty prawami autorskimi i jest oprogramowaniem wolnym.
Proszę zapoznać się z plikiem COPYRIGHT, który dołączono do pakietu Slirp,
aby poznać warunki związane z prawami autorskimi.

Adres IP hosta Slirp: 127.0.0.1
Adres IP twojego DNS(u): 1.1.1.1, 10.77.0.7
Twój adres to 10.0.2.15
(lub cokolwiek innego, co chcesz)

Wpisz pięć zer (0), aby wyjść.

[autodetect SLIP\/CSLIP, MTU 1500, MRU 1500]

SLiRP Gotowy ...

Nie pada — więc powinno działać! Spróbujmy umieścić ten binarny plik w ~\/bin\/slirp:

cp slirp ~\/bin\/slirp

Na wypadek, gdyby twórca pakietu go usunął, zrobiłam lustrzane odbicie.

Konfiguracja sieci

Teraz skonfigurujmy sieć na naszym gościnym jądrze. Zaktualizujmy parametry uruchamiania:

linux 
  root=\/dev\/root 
  rootfstype=hostfs 
  rootflags=$HOME\/prefix\/uml-demo 
  rw 
  mem=64M 
  eth0=slirp,,$HOME\/bin\/slirp 
  init=\/bin\/sh

Teraz włączmy sieć:

mount -t proc proc proc\/\nmount -t sysfs sys sys\/\n\nifconfig eth0 10.0.2.14 netmask 255.255.255.240 broadcast 10.0.2.15
route add default gw 10.0.2.2

Pierwsze dwie komendy konfiguracji /proc i /sys są niezbędne do działania ifconfig, która ustawia interfejs sieciowy do komunikacji ze Slirp. Komenda trasa ustawia tabelę routingu jądra, aby wymusić przesyłanie całego ruchu przez tunel Slirp. Sprawdźmy to za pomocą zapytania DNS:

nslookup google.com 8.8.8.8\nSerwer:    8.8.8.8\nAdres 1: 8.8.8.8 dns.google\n\nNazwa:      google.com\nAdres 1: 172.217.12.206 lga25s63-in-f14.1e100.net\nAdres 2: 2607:f8b0:4006:81b::200e lga25s63-in-x0e.1e100.net

Działa!

Przyp. red.: Wygląda na to, że oryginalny post był pisany na desktopie z przewodową kartą sieciową lub inną konfiguracją, która nie wymaga dodatkowych sterowników. Na laptopie z WiFi 8265 od Intela przy uruchamianiu sieci pojawia się błąd.

/ # ifconfig eth0 10.0.2.14 netmask 255.255.255.240 broadcast 10.0.2.15
slirp_tramp failed - errno = 2
ifconfig: ioctl 0x8914 failed: No such file or directory
/ #

Wygląda na to, że jądro nie może połączyć się ze sterownikiem karty sieciowej. Próba skompilowania firmware w jądrze niestety nie pomogła. W momencie publikacji w takiej konfiguracji nie znalazłem jeszcze rozwiązania. W prostszych konfiguracjach (na przykład w Virtualbox) interfejs działa poprawnie.

Zautomatyzujmy przekierowanie za pomocą następującego skryptu powłoki:

#!/bin/sh
# init.sh

mount -t proc proc proc/
mount -t sysfs sys sys/
ifconfig eth0 10.0.2.14 netmask 255.255.255.240 broadcast 10.0.2.15
route add default gw 10.0.2.2

echo "networking set up"

exec /tini /bin/sh

I oznaczmy go jako wykonywalny:

chmod +x init.sh

A następnie wprowadźmy zmiany w linii poleceń jądra:

linux 
  root=\/dev\/root 
  rootfstype=hostfs 
  rootflags=$HOME\/prefix\/uml-demo 
  rw 
  mem=64M 
  eth0=slirp,,$HOME\/bin\/slirp 
  init=\/init.sh

A następnie powtórzmy:

SLiRP Gotowe ...
konfiguracja sieci ustawiona
\/bin\/sh: can't access tty; kontrola zadań wyłączona

nslookup google.com 8.8.8.8
Serwer:    8.8.8.8
Adres 1: 8.8.8.8 dns.google

Nazwa:      google.com
Adres 1: 172.217.12.206 lga25s63-in-f14.1e100.net
Adres 2: 2607:f8b0:4004:800::200e iad30s09-in-x0e.1e100.net

Sieć działa stabilnie!

Plik Docker

Aby ułatwić Ci wszystko to, zebrałam Dockerfile, który automatyzuje większość opisanych kroków i powinien zapewnić Ci działającą konfigurację. Mam też gotową konfigurację jądra, która zawiera wszystko, co opisano w poście. Ważne jest, aby zrozumieć, że tutaj przedstawiłam tylko minimalną konfigurację.

Mam nadzieję, że ten post pomógł Ci zrozumieć, jak uruchomić gościnne jądro. Wyszło coś dużego, ale publikacja miała być wszechstronnym przewodnikiem na temat budowy, instalacji i konfiguracji Trybu Użytkownika w Linuksie dla nowoczesnych wersji systemów operacyjnych tej rodziny. Kolejne kroki powinny obejmować instalację usług i oprogramowania już wewnątrz gościnnego systemu. Ponieważ obrazy kontenerów Docker to po prostu rozpakowane archiwa tar, powinieneś być w stanie wyodrębnić obraz za pomocą docker export, a następnie określić ścieżkę jego instalacji w katalogu głównym systemu plików gościnnego jądra. Potem wykonaj skrypt powłoki.

Szczególne podziękowania dla Rkeene z #lobsters na Freenode. Bez jego pomocy w debugowaniu Slirp nie doszedłbym tak daleko. Nie mam pojęcia, jak jego system Slackware działa poprawnie z slirp, ale moje systemy Ubuntu i Alpine nie przyjęły slirp i zaproponowany mi przez Rkeene plik binarny. Ale wystarcza mi, że cokolwiek działa.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster