Как да започнете да използвате User Mode в Linux

Въведение от преводача: На фона на масовото навлизане на различни контейнери в живота ни, може да бъде доста интересно и полезно да научим, от какви технологии всичко това е започнало. Някои от тях все още могат да бъдат използвани и днес, но не всички помнят (или знаят, ако не са били свидетели на бурното им развитие). Една от тези технологии е User Mode Linux. Авторът на оригинала е провел задълбочено проучване, за да разбере какво от старите разработки все още работи и какво не, и е събрал нещо като стъпка по стъпка инструкция за това как да си направите домашен UML през 2019 година. И да, поканихме за Хабр автора на оригиналния пост. Cadey, така че ако имате въпроси — задавайте на английски в коментарите.

Как да започнете да използвате User Mode в Linux

User Mode в Linux всъщност е порт на ядрото на Linux върху самото себе си. Режимът позволява стартиране на полноценно ядро на Linux като потребителски процес и обикновено се използва от разработчици за тестване на драйвери. Но този режим е полезен и като инструмент за обща изолация, принципът на който е сходен с работата на виртуалните машини. Този режим осигурява по-голяма изолация от Docker, но по-малка от пълноценна виртуална машина, като KVM или Virtual Box.

Като цяло, User Mode може да изглежда странен и сложен за използване инструмент, но все пак има свои области на приложение. Това е пълноценно ядро на Linux, работещо от непривилегирован потребител. Тази характеристика позволява стартиране на потенциално ненадежден код без никакви заплахи за хост машината. А тъй като е пълноценно ядро, неговите процеси са изолирани от хост машината, а именно процесите, работещи в рамките на User Mode, няма да бъдат видими за хост система. Това не е като обичайния Docker контейнер, при който хост машината винаги вижда процесите вътре в хранилището. Погледнете този фрагмент от pstree от един от моите сървъри:

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}]

И сравнете това с pstree на ядрото на Linux в потребителски режим:

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

При работа с Docker контейнери, мога да виждам от хоста имената на процесите, които са стартирани в гостуващата система. С Linux User Mode това е невъзможно. Какво означава това? Това означава, че инструментите за мониторинг, работещи чрез подсистемата за одит на Linux (Linux’s auditing subsystem) не виждат процесите, изпълнявани в гостуващата система. Но в някои ситуации тази особеност може да стане двуостра работа.

Целият пост по-долу е набор от изследвания и груби опити да се постигне желания резултат. За това ми се наложи да използвам различни стари инструменти, да чета изходния код на ядрото, да се занимавам с интензивна отладка на код, написан в епохата, когато все още ходех в начално училище, както и да се ровя в сборките на Heroku с помощта на специален бинарник в търсене на необходимите инструменти. Всичка тази работа доведе до това, че момчетата в IRC започнаха да ме наричат магьосница (magic). Надявам се, че този пост ще послужи на някого като надеждна документация за това как да направи същото, но вече с по-нови ядра и версии на ОС.

Настройка

Настройката на Linux User Mode се извършва в няколко етапа:

  • инсталиране на зависимости на хоста;
  • сваляне на ядрото на Linux;
  • настройка на конфигурацията на ядрото;
  • сборка на ядрото;
  • инсталация на бинарника;
  • настройка на файловата система на госта;
  • определяне на параметрите за стартиране на ядрото;
  • настройка на мрежата на госта;
  • стартиране на гостуващото ядро.

Предполагам, че ако решите да опитате сами, вероятно ще правите всичко описано в някаква система, подобна на Ubuntu или Debian. Опитвах се да реализирам всичко по-гореописано в любимия си дистрибутив — Alpine, но нищо не се получи, вероятно поради факта, че ядрото на Linux има строга зависимост от glibc-isms за драйвери в потребителски режим. Планирам да съобщя за това в upstream, след като окончателно разбера проблема.

Инсталиране на зависимости на хоста

Ubuntu изисква поне следните пакети за компилиране на ядрото на Linux (при условие на чиста инсталация):

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

Можете да ги инсталирате с помощта на следната команда (с root права или с помощта на sudo):

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

Обърнете внимание, че стартирането на програмата за настройка на менюто за ядрото на Linux ще изисква инсталиране на libncurses-dev. Моля, уверете се, че той е инсталиран с помощта на следната команда (с root права или с помощта на sudo):

apt-get -y install libncurses-dev

Изтегляне на ядрото

Определете местоположението за изтегляне и последваща компилация на ядрото. За тази операция ще ви е необходимо около 1.3 GB пространство на твърдия диск, така че се уверете, че разполагате с него.

След това отидете на kernel.org и вземете URL адреса за изтегляне на последната стабилна версия на ядрото. Към момента на писане на поста това е: https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.1.16.tar.xz

Изтеглете този файл, използвайки 'wget':

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

И го извлечете с помощта на 'tar':

tar xJf linux-5.1.16.tar.xz

Сега влезте в директорията, създадена при разархивирането на tarball:

cd linux-5.1.16

Настройка на компилацията на ядрото

Системата за компилиране на ядрото е набор от Make-файлове с с множество потребителски инструменти и скриптове за автоматизация на процеса. За начало отворете интерактивната програма за настройка:

make ARCH=um menuconfig

Тя частично ще проведе компилацията и ще ви изведе диалогово прозорче. Когато в долната част на прозорцето се появи ‘[Select]‘, ще можете да се захванете с настройките с клавишите Space или Enter. Навигацията в прозореца става, както обикновено, с клавишите стрелки «нагоре» и «надолу», а избора на елементи — «наляво» или «надясно».

Иконката вида —> означава, че сте в подменю, входът в което става с клавиша Enter. Изходът от него, очевидно, е чрез ‘[Exit]‘.

Включете следните параметри в ‘[Select]‘ и се уверете, че до тях е символ ‘[*]’:

Опции, специфичные для UML:
  - Хост файловой системы
Поддержка сетей (включите это, чтобы меню появилось):
  - Сетевые настройки:
    - TCP/IP Сетевое взаимодействие
Устройства сети UML:
  - Виртуальное сетевое устройство
  - SLiRP транспорт

Отсюда можно выходить, последовательно выбирая ‘[Exit]‘. Убедитесь, что в конце вам предложат сохранить конфигурацию и выберите ‘[Да]‘.

Рекомендую вам поэкспериментировать с параметрами сборки ядра после чтения этого поста. Благодаря этим экспериментам вы сможете лучше понять низкоуровневые механики ядра и влияние различных флагов на его сборку.

Сборка ядра

Ядро Linux — это большая программа, занимающаяся множеством задач. Даже при такой минимальной конфигурации на старом оборудовании его сборка может занять довольно много времени. Поэтому собирайте ядро с помощью следующей команды:

make ARCH=um -j$(nproc)

Зачем? Эта команда указывает нашему сборщику использовать все доступные ядра и потоки процессора в процессе сборки. Команда $(nproc) в конце сборки подставляет вывод команды nproc, которая является частью coreutils в стандартной сборке Ubuntu.

После некоторого времени наше ядро будет собрано в исполняемый файл ./linux.

Инсталляция бинарника

Так как User Mode в Linux создает обычный бинарник, вы можете установить его так же, как любую другую утилиту. Вот как я это делала:

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

Также стоит убедиться, что ~/bin находится в вашем $PATH:

export PATH=$PATH:$HOME/bin

Настройка гостевой файловой системы

Создайте директорию для гостевой файловой системы:

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

Откройте alpinelinux.org и в разделе загрузок найдите актуальную ссылку на скачивание MINI ROOT FILESYSTEM. На момент написания публикации это было:

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

Скачайте этот tarball, используя 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

Теперь войдите в директорию гостевой файловой системы и распакуйте архив:

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

Эти действия создадут маленький шаблон файловой системы. Из-за особенностей работы системы устанавливать пакеты через диспетчер apk Alpine будет крайне сложно. Но данной файловой системы будет достаточно для оценки общей идеи.

Также нам потребуется инструмент tini для предотвращения потребления памяти зомби-процессами нашего гостевого ядра.

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

Создание командной строки ядра

В ядрото на Linux, както и в повечето други програми, има аргументи за команден ред, с които можете да се запознаете, като посочите ключа --help.

Сам —help

linux --help
User Mode Linux v5.1.16
        наличен на http://user-mode-linux.sourceforge.net/

--showconfig
    Печати конфигурационния файл, от който е генериран този UML бинарен файл.

iomem=,
    Конфигурирайте  като IO мемориен регион с име .

mem=
    Това контролира колко "физическа" памет ядрото разпределя
    за системата. Размерът се посочва като число, последвано от
    едно от 'k', 'K', 'm', 'M', които имат очевидни значения.
    Това не е свързано с количеството памет на хоста. То може
    да бъде повече, и излишъкът, ако е използван, ще бъде просто разбит.
        Пример: mem=64M

--help
    Печати това съобщение.

debug
    този флаг не е необходим, за да се стартира gdb на UML в skas режим

root=
    Това всъщност се използва от общото ядро точно по същия
    начин, както в която и да е друга ядрена. Ако конфигурирате няколко блокови
    устройства и искате да стартирате от нещо различно от ubd0, ще
    използвате нещо като:
        root=/dev/ubd5

--version
    Печати номер на версията на ядрото.

umid=
    Това се използва, за да се зададе уникална идентичност на тази UML машина и
    се използва за именуване на pid файла и сокета на управленската конзола.

con[0-9]*=
    Свържете конзола или сериен ред към хост канал. Вижте
    http://user-mode-linux.sourceforge.net/old/input.html за пълно
    описание на този ключ.

eth[0-9]+=,
    Конфигурирайте мрежово устройство.
    
aio=2.4
    Това се използва, за да принуди UML да използва 2.4-стил AIO дори когато 2.6 AIO е
    наличен. 2.4 AIO е един поток, който обработва едно искане по
    време, синхронно. 2.6 AIO е поток, който използва 2.6 AIO
    интерфейса, за да обработва произволен брой висящи искания. 2.6 AIO
    не е наличен в tt режим, на 2.4 хостове или когато UML е построен с
    /usr/include/linux/aio_abi.h не е наличен. Много дистрибуции не
    включват aio_abi.h, така че ще трябва да го копирате от дерева
    на ядро в /usr/include/linux, за да изградите AIO-способен UML

nosysemu
    Изключва емилация на системни повиквания за ptrace (SYSEMU).
    SYSEMU е патч за производителност, въведен от Лоран Вивиер. Променя
    поведението на ptrace() и помага за намаляване на скоростите на
    превключване на контекста на хоста.
    За да работи, ви е необходим и патч на ядрото за вашия хост.
    Вижте http://perso.wanadoo.fr/laurent.vivier/UML/ за допълнителна
    информация.

uml_dir=
    Мястото за разположение на pid и umid файловете.

quiet
    Изключва информационните съобщения по време на стартиране.

hostfs=,,...
    Използва се за задаване на параметрите на hostfs. Аргументът на кореновата директория
    се използва, за да ограничи всички hostfs монтирания в зададеното
    дървовидно структура на хоста. Ако това не е зададено, потребител
    вътре в UML може да монтира всичко на хоста, до което има
    достъп потребителят, който го стартира.
    Единственият флаг, който в момента се поддържа, е 'append', който
    указва, че всички файлове, отворени от hostfs, ще бъдат отворени в
    режим на добавяне.

Този флаг осветява основните параметри на стартирането. Нека стартираме ядрото с минимално необходимия набор от опции:

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

Редовете по-горе казват на нашето ядро следното:

  • Да предположим, че кореновата файлова система е псевдо-устройство. /dev/root.
  • Избери hostfs като драйвер на основната файлова система.
  • Монтирайте гостуващата файлова система, която създадохме в root устройството.
  • И да, в режим на четене и запис.
  • Използвайте само 64 мегабайта оперативна памет (можете да използвате значително по-малко, в зависимост от това, което планирате да правите, но 64 МБ изглеждат оптималното количество).
  • Ядрото автоматично стартира /bin/sh като init-процес.

Изпълнете тази команда и трябва да получите нещо като следното:

Още един лист

Ограничения на сървърния дъмп:
        меко - 0
        твърдо - НЯМА
Проверка дали ptrace може да променя системните повиквания...ОК
Проверка за пач за емулация на системните повиквания за ptrace...ОК
Проверка за напреднал пач за емулация на системните повиквания за ptrace...ОК
Проверка на променливите на средата за временно директория...не са намерени
Проверка дали /dev/shm е на tmpfs...ОК
Проверка PROT_EXEC mmap в /dev/shm...ОК
Добавяне на 32137216 байта към физическата памет, за да се компенсира пропуск в exec-shield
Версия на Linux 5.1.16 (cadey@kahless) (gcc версия 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) #30 Неделя, 7 юли 2019 г. 18:57:19 UTC
Създадени 1 зонови списъци, групиране на мобилност. Общо страници: 23898
Команден ред на ядрото: root=/dev/root rootflags=/home/cadey/dl/uml/alpine rootfstype=hostfs rw mem=64M init=/bin/sh
Хеш таблица на кеша за директории: 16384 (порядък: 5, 131072 байта)
Хеш таблица на кеша на inode: 8192 (порядък: 4, 65536 байта)
Памет: 59584K/96920K налична (2692K код на ядрото, 708K rwdata, 588K rodata, 104K init, 244K bss, 37336K резервирани, 0K cma-резервирани)
SLUB: HWalign=64, Поръчка=0-3, MinObjects=0, CPUs=1, Възли=1
NR_IRQS: 15
source на времето: таймер: маска: 0xffffffffffffffff макс. цикли: 0x1cd42e205, макс. idle_ns: 881590404426 нс
Калибриране на забавяне... 7479.29 BogoMIPS (lpj=37396480)
pid_max: по подразбиране: 32768 минимум: 301
Хеш таблица на кеша за монтиране: 512 (порядък: 0, 4096 байта)
Хеш таблица на кеша за точките за монтиране: 512 (порядък: 0, 4096 байта)
Проверка дали host ptys поддържат изход SIGIO...Да
Проверка дали host ptys поддържат SIGIO при затваряне...Не, активиране на решение
devtmpfs: инициализирано
random: get_random_bytes извикано от setup_net+0x48/0x1e0 с crng_init=0
Използване на 2.6 host AIO
source на времето: jiffies: маска: 0xffffffff максимални цикли: 0xffffffff, макс. idle_ns: 19112604462750000 нс
футекс хеш таблица: 256 (порядък: 0, 6144 байта)
NET: Регистрирано семейство протоколи 16
source на времето: Превключено на source на времето таймер
NET: Регистрирано семейство протоколи 2
tcp_listen_portaddr_hash хеш таблица: 256 (порядък: 0, 4096 байта)
TCP установена хеш таблица: 1024 (порядък: 1, 8192 байта)
TCP хеш таблица за свързване: 1024 (порядък: 1, 8192 байта)
TCP: Хеш таблиците конфигурирани (установени 1024 свързване 1024)
UDP хеш таблица: 256 (порядък: 1, 8192 байта)
UDP-Lite хеш таблица: 256 (порядък: 1, 8192 байта)
NET: Регистрирано семейство протоколи 1
конзола [stderr0] деактивирана
mconsole (версия 2) инициализирана на /home/cadey/.uml/tEwIjm/mconsole
Проверка на поддръжка на host MADV_REMOVE...ОК
работен набор: timestamp_bits=62 max_order=14 bucket_order=0
Блоков слой SCSI генерален (bsg) драйвер версия 0.4 зареден (основен 254)
планиратор на операции noop регистриран (по подразбиране)
планиратор на операции bfq регистриран
loop: модулът зареден
NET: Регистрирано семейство протоколи 17
Инициализирана стандартна конзола драйвер
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 1 : Конфигурацията е неуспешна
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 2 : Конфигурацията е неуспешна
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 3 : Конфигурацията е неуспешна
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 4 : Конфигурацията е неуспешна
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 5 : Конфигурацията е неуспешна
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 6 : Конфигурацията е неуспешна
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 7 : Конфигурацията е неуспешна
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 8 : Конфигурацията е неуспешна
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 9 : Конфигурацията е неуспешна
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 10 : Конфигурацията е неуспешна
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 11 : Конфигурацията е неуспешна
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 12 : Конфигурацията е неуспешна
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 13 : Конфигурацията е неуспешна
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 14 : Конфигурацията е неуспешна
Използване на тип канал, който е конфигуриран извън UML
setup_one_line неуспех за устройство 15 : Конфигурацията е неуспешна
Конзолата е инициализирана на /dev/tty0
конзола [tty0] активирана
конзола [mc-1] активирана
Неуспешно инициализиране на ubd устройство 0 :Не може да се определи размерът на файла на устройството
VFS: Монтиране на корен (hostfs файловата система) на устройство 0:11.
devtmpfs: монтирано
Тази архитектура няма защита на паметта на ядрото.
Стартиране на /bin/sh като процес init
/bin/sh: не може да се получи достъп до tty; управлението на работата е изключено
random: бързо инициализиране завършено
/ # 

Манипулации по-горе ще ни дадат хоства система на минималном ниво, без такива неща, като /proc или присвоен хостнейм. За пример, опитайте да изпълните следните команди:

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

За да излезете от хоства система, напишете exit или натиснете control-d. Това ще уби-оболочката с последващ kernel panic:

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

Получихме този kernel panic поради факта, че ядрото Linux счита, че процесът на инициализация винаги е активен. Без него системата повече не може да функционира и спира. Но тъй като това е процес на потребителски режим, резултатът сам изпраща себе си в SIGABRT, което води до изход.

Настройка на хоства мрежа

А тук нещата започват да не вървят по план. Мрежата в User Mode Linux — това е мястото, където цялата концепция за ограничен «потребителски режим» започва да се разпада. На системно ниво мрежата обикновено е ограничена привилегированими режими на изпълнение по всички ни известни причини.

Поч. заб.: повече за различните варианти на работа с мрежата в UML може да се прочете тук..

Пътуване в slirp

Въпреки това, съществува древен и практически незаслужен инструмент на име Slirp, чрез който User Mode Linux може да взаимодейства с мрежата. Той работи приблизително като стек TCP/IP на ниво потребител и не изисква никакви системни разрешения за работа. Този инструмент е пуснат през 1995 година, а последната актуализация е от 2006 година. Slirp е много стар. През времето на липса на поддръжка и актуализации компилаторите толкова се развиха, че сега инструментът може да бъде описан само като "code rot".

И така, нека инсталираме Slirp от репозиторите на Ubuntu и опитаме да го пуснем:

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

Copyright (c) 1995,1996 Danny Gasparovski и други.
Всички права запазени.
Тази програма е защитена с авторски права, безплатен софтуер.
Моля, прочетете файла COPYRIGHT, който идва с пакета Slirp
за условията на авторските права.

IP адрес на хост Slirp: 127.0.0.1
IP адрес на вашите DNS: 1.1.1.1, 10.77.0.7
Вашият адрес е 10.0.2.15
(или каквото друго искате)

Напишете пет нули (0), за да излезете.

[автоопределяне SLIP/CSLIP, MTU 1500, MRU 1500, 115200 baud]

SLiRP Готов ...
fish: "/usr/bin/slirp" е прекратен от сигнал SIGSEGV (Грешка в адресна граница)

Ох, божички. Нека инсталираме отладчик за Slirp и да видим дали можем да разберем какво тук става:

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.

Грешка появляется у нас в этой строке. Нека видим стека на грешки, може там нищо не помогне:

(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.

Тук виждаме, че сривът настъпва по време на стартиране на основния цикъл, когато slirp се опитва да провери таймаутите. В този момент трябваше да се откажа от опитите за отстраняване на грешки. Но нека видим дали Slirp, компилиран от източници, ще работи. Свалих отново архива директно от сайта Sourceforge, защото да теглим нещо оттам през командния ред е болка:

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

Тук виждаме предупреждения за неопределени вградени функции, тоест за невъзможност да се свърже получаващият се бинарен файл. Изглежда, че между 2006 година и този момент gcc е спрял да създава символите, използвани в вградени функции на междинно компилирани файлове. Нека опитаме да заменим ключовата дума inline с празен коментар и да видим резултата:

vi slirp.h
:6
a

#define inline /**/

:wq
make

Не. И това не работи. Все още не може да се намерят символите на тези функции.

На този етап се предадох и започнах да търся в Github пакети за компилиране на Heroku. Моята теория беше, че в някой от пакетите за компилиране на Heroku трябва да има необходимите за мен бинарни файлове. В крайна сметка търсенето ме отведе тук. Изтеглих и разархивирах uml.tar.gz и намерих следното:

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*

Това е бинарен файл slirp! А той работи?

./slirp
Slirp v1.0.17 (BETA) FULL_BOLT

Copyright (c) 1995,1996 Danny Gasparovski и други.
Всички права запазени.
Тази програма е защитена с авторски права, безплатен софтуер.
Моля, прочетете файла COPYRIGHT, който подлежи на пакета Slirp
за условията и условията на авторските права.

IP адрес на хоста Slirp: 127.0.0.1
IP адрес на вашия DNS: 1.1.1.1, 10.77.0.7
Вашият адрес е 10.0.2.15
(или каквото и да искате)

Напишете пет нули (0), за да излезете.

[автооткриване SLIP/CSLIP, MTU 1500, MRU 1500]

SLiRP Готови ...

Не пада — така че трябва да проработи! Нека качим този бинарник в ~/bin/slirp:

cp slirp ~/bin/slirp

В случай, че създателят на пакета реши да го премахне, аз направих огледало..

Настройка на мрежата

Сега нека настроим мрежата на нашето гостоприемно ядро. Нека обновим параметрите за стартиране:

linux 
  root=/dev/root 
  rootfstype=hostfs 
  rootflags=$HOME/prefix/uml-demo 
  rw 
  mem=64M 
  eth0=slirp,,$HOME/bin/slirp 
  init=/bin/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

Първите две команди за настройка /proc и /sys са необходими за работа. ifconfig, която определя мрежовия интерфейс за връзка със Slirp. Командата route настройва таблицата за маршрутизиране на ядрото, за да принуди целия трафик да преминава през тунела Slirp. Нека проверим това с DNS заявка:

nslookup google.com 8.8.8.8
Сървър:    8.8.8.8
Адрес 1: 8.8.8.8 dns.google

Име:      google.com
Адрес 1: 172.217.12.206 lga25s63-in-f14.1e100.net
Адрес 2: 2607:f8b0:4006:81b::200e lga25s63-in-x0e.1e100.net

Работи!

Бележка: Изглежда, че оригиналната публикация е била написана на десктоп с проводна мрежова карта или друга конфигурация, която не изисква допълнителни драйвери. На лаптоп с WiFi 8265 от Intel обаче при настройка на мрежата се появява грешка.

/ # 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
/ #

Очевидно, ядро не может связаться с драйвером сетевой карты. Попытка встроить прошивку в ядро ситуацию, к сожалению, не исправила. На момент публикации в такой конфигурации решение пока не найдено. В более простых конфигурациях (например, в Virtualbox) интерфейс поднимается корректно.

Давайте автоматизируем перенаправление с помощью следующего shell-скрипта:

#!/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

И отметим его исполняемым:

chmod +x init.sh

А затем внесем изменения в командную строку ядра:

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

И повторим:

SLiRP Ready ...
networking set up
\/bin\/sh: can't access tty; job control turned off

nslookup google.com 8.8.8.8
Server:    8.8.8.8
Address 1: 8.8.8.8 dns.google

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

Сеть работает стабильно!

Docker-файл

Чтобы вам было проще все это проверить, я собрала Dockerfile, который автоматизирует большинство описанных шагов и должен обеспечить вам рабочую конфигурацию. Еще у меня есть готовая конфигурация ядра, в которой есть все, что описано в посте. Но важно понимать, что тут я изложила только минимальную настройку.

Я надеюсь, что этот пост помог вам понять, как поднять гостевое ядро. Получился какой-то монстр, но публикация задумывалась как всеобъемлющее руководство на тему сборки, установки и настройки User Mode в Linux под современными версиями операционных систем этого семейства. Последующие действия должны включать в себя установку сервисов и прочего программного обеспечения уже внутри гостевой системы. Так как образы контейнеров Docker — это просто распакованные tar-архивы, вы должны быть в состоянии извлечь образ через docker export, а затем определить путь его установки в корне файловой системы гостевого ядра. Ну, а затем выполните shell-скрипт.

Отдельное спасибо Rkeene с #lobsters на Freenode. Без его помощи в отладке Slirp я бы не зашла так далеко. Я понятия не имею, как его система Slackware корректно работает со slirp, но мои системы Ubuntu и Alpine не приняли slirp и предложенный мне Rkeene бинарник. Но мне достаточно и того, что у меня работает хоть что-то.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster