Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Вступление от переводчика: На фоне массового входа в нашу жизнь различного рода контейнеров может быть довольно интересно и полезно узнать, с каких технологий это всё начиналось когда-то. Некоторые из них можно с пользой применять и по сей день, но не все о таких способах помнят (или знают, если не застали во время их бурного развития). Одной из таких технологий является User Mode Linux. Автор оригинала изрядно покопалась, разбираясь, что из старых наработок ещё работает, а что уже не очень, и собрала нечто вроде пошаговой инструкции о том, как самому себе завести доморощенный UML в 2к19. И да, мы пригласили на Хабр автора оригинального поста Cadey, так что если есть вопросы — задавайте на английском в комментариях.
User Mode в Linux — это, фактически, порт ядра Linux на само себя. Этот режим позволяет запустить полноценное ядро Linux в качестве пользовательского процесса и обычно используется разработчиками для тестирования драйверов. Но также этот режим полезен и в качестве инструмента общей изоляции, принцип которой схож с работой виртуальных машин. Данный режим обеспечивают большую изоляцию, чем Docker, но меньшую, чем полноценная виртуальная машина вроде KVM или Virtual Box.
В целом, User Mode может показаться странным и сложным в использовании инструментом, но у него всё же есть свои области применения. Ведь это полноценное Linux-ядро, работающее от непривилегированного пользователя. Эта особенность позволяет запускать потенциально ненадежный код без каких-либо угроз для хост-машины. А поскольку это полноценное ядро, его процессы изолированы от хост-машины, то есть процессы, работающие внутри User Mode, не будут видны для хоста. Это не похоже на привычный Docker-контейнер, в случае которого хост-машина всегда видит процессы внутри хранилища. Посмотрите на этот кусок pstree с одного из моих серверов:
И сравните это с pstree ядра Linux в User Mode:
При работе с Docker-контейнерами я могу видеть с хоста имена процессов, которые запущены в гостевой системе. С Linux User Mode это невозможно. Что это значит? Это значит, что инструменты мониторинга, работающие через подсистему аудита Linux (Linux’s auditing subsystem) не видят процессы, исполняемые в гостевой системе. Но в некоторых ситуациях эта особенность может стать палкой о двух концах.
Вообще весь пост ниже — это набор исследований и грубых попыток добиться желаемого результата. Для этого мне приходилось использовать разные древние инструменты, читать исходники ядра, заниматься интенсивной отладкой кода, написанного во времена, когда я еще ходила в начальную школу, а также ковыряться в сборках Heroku с помощью специального бинаря в поисках нужных мне инструментов. Вся эта работа привела к тому, что ребята в моем IRC стали называть меня волшебницей (magic). Я надеюсь, что этот пост послужит кому-то надежной документацией для того, чтобы провернуть все тоже самое, но уже с более новыми ядрами и версиями ОС.
Настройка Linux User Mode выполняется в несколько этапов:
Я предполагаю, что если вы решите самостоятельно это всё провернуть, скорее всего, будете делать все описанное в какой-нибудь Ubuntu или Debian-подобной системе. Я пыталась реализовать все вышеописанное в моем любимом дистрибутиве — Alpine, но ничего не вышло, по всей видимости, по причине того, что ядро Linux имеет жесткую привязку по glibc-isms для драйверов в User Mode. Планирую сообщить об этом в апстрим после того, как окончательно разберусь в проблеме.
Ubuntu требует, как минимум, следующие пакеты для сборки ядра Linux (при условии чистой установки):
Вы можете установить их с помощью следующей команды (с правами root или с помощью sudo):
Обратите внимание, что запуск программы настройки меню для ядра Linux потребует установки
Определите место для загрузки и последующей сборки ядра. Для этой операции вам потребуется выделить около 1,3 Гб пространства на жестком диске, поэтому убедитесь, что оно у вас есть.
После перейдите на kernel.org и получите URL на загрузку последней стабильной версии ядра. На момент написания поста это: https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.1.16.tar.xz
Загрузите этот файл, используя
И извлеките его с помощью
Теперь входим в директорию, созданную при распаковке tarball:
Система сборки ядра — это набор Make-файлов с множеством пользовательских инструментов и скриптов для автоматизации процесса. Для начала откройте интерактивную программу настройки:
Она частично проведет сборку и выведет вам диалоговое окно. Когда внизу окна высветится '
Указатель вида ---> означает, что вы находитесь в подменю, вход в которое осуществляется клавишей Ввод. Выход из него, очевидно, через '
Включите следующие параметры в '
Все, из этого окна можно выходить, последовательно выбирая '
Я рекомендую вам поиграться с параметрами сборки ядра после прочтения этого поста. Благодаря этим экспериментами вы сможете многое почерпнуть в плане понимания работы низкоуровневых механик ядра и влияния различных флагов на его сборку.
Ядро Linux — это большая программа, занимающаяся множеством вещей. Даже при такой минимальной конфигурации на старом оборудовании его сборка может занять достаточно времени. Поэтому собирайте ядро с помощью следующей команды:
Зачем? Эта команда скажет нашему сборщику использовать все доступные ядра и потоки процессора в процессе сборки. Команда
По прошествии некоторого времени наше ядро будет собрано в исполняемый файл
Так как User Mode в Linux создает обычный бинарник, вы можете установить его, как и любую другую утилиту. Вот как это делала я:
Также стоит убедиться, что
Создайте директорию для гостевой файловой системы:
Откройте alpinelinux.org и в разделе загрузок найдите актуальную ссылку на скачивание
Скачайте этот tarball, используя wget:
Теперь войдите в директорию гостевой файловой системы и распакуйте архив:
Описанные действия создадут маленький шаблон файловой системы. Из-за особенностей работы системы устанавливать пакеты через диспетчер apk Alpine будет крайне сложно. Но данной ФС будет достаточно для оценки общей идеи.
Также нам потребуется инструмент tini для пресечения потребления памяти зомби-процессами нашего гостевого ядра.
В ядре Linux, как и в большинстве других программ, есть аргументы командной строки, с которыми можно ознакомиться, указав ключ
Это полотнище освещает основные параметры запуска. Давайте запустим ядро с минимальным необходимым набором опций:
Строки выше говорят нашему ядру следующее:
Запустите эту команду, и вы должны получить что-то вроде следующего:
Манипуляции выше дадут нам гостевую систему на минималках, без таких вещей, как
Чтобы выйти из гостевой системы, введите
Мы получили этот kernel panic по причине того, что ядро Linux считает, что процесс инициализации всегда запущен. Без него система больше не может функционировать и завершает работу. Но так как это процесс пользовательского режима, полученный результат отправляет сам себя в
А вот тут у нас всё начинает идти не по плану. Сеть в User Mode Linux — это то место, где вся концепция ограниченного «пользовательского режима» начинает разваливаться. Ведь обычно на системном уровне сеть ограничена привилегированными режимами исполнения по всем нам понятным причинам.
Прим. пер.: больше о разных вариантах работы с сетью в UML можно почитать здесь.
Однако же существует древний и практически неподдерживаемый инструмент под названием Slirp, при помощи которого User Mode Linux может взаимодействовать с сетью. Он работает примерно как стек TCP/IP на уровне пользователя и не требует каких-либо системных разрешений для запуска. Этот инструмент был выпущен в 1995 году, а последнее обновление датируется 2006 годом. Slirp очень стар. За время без поддержки и обновлений компиляторы ушли настолько далеко, что теперь этот инструмент можно охарактеризовать только как «code rot».
Итак, давайте накатим Slirp из репозиториев Ubuntu и попробуем его запустить:
Ох, божечки. Давайте установим отладчик для Slirp и посмотрим, сможем ли разобраться, что тут происходит:
Ошибка бьется у нас в этой строке. Давайте посмотрим на stacktrace, может там нам что-нибудь поможет:
Тут мы видим, что сбой происходит во время запуска основного цикла, когда slirp пытается проверить тайм-ауты. Вот в этот момент я должна была отказаться от попыток отладки. Но давайте посмотрим, работает ли Slirp, собраный из сорцов. Я повторно загрузила архив напрямую с сайта Sourceforge, потому что тащить что-то оттуда через командную строку — боль:
Тут мы видим алерты о неопределенных встроенных функциях, то есть о невозможности слинковать получающийся в итоге бинарный файл. Похоже, в период между 2006 годом и этим моментом gcc прекратил создавать символы, используемые в строенных функциях промежуточно скомпилированных файлов. Давайте попробуем заменить ключевое слово
Не-а. Это тоже не работает. По-прежнему не удается найти символы этих функций.
На этом этапе я сдалась и начала искать на Github пакеты сборки Heroku. Моя теория базировалась на том, что в каком-нибудь сборочном пакете Heroku будут содержаться необходимые мне двоичные файлы. В итоге поиски привели меня вот сюда. Я скачала и распаковала
Это бинарный файл slirp! А он работает?
Не падает — так что должно сработать! Давайте подсадим этот бинарник в
На случай, если создатель пакета удалит его, я сделала зеркало.
Теперь давайте настроим сеть на нашем гостевом ядре. Обновим параметры запуска:
Теперь давайте включим сеть:
Первые две команды настройки
Работает!
Прим.пер.: Судя по всему, изначальный пост писался на десктопе с проводной сетевой картой, либо какой-то иной конфигурацией, не требующей дополнительных драйверов. На ноутбуке с WiFi 8265 от Intel же при поднятии сети возникает ошибка
Видимо, ядро не может связаться с драйвером сетевухи. Попытка вкомпилить firmware в ядро ситуацию, к сожалению, не исправила. На момент публикации именно в такой конфигурации решения пока найти не удалось. На более простых конфигах (например, в Virtualbox) интерфейс поднимается корректно.
Давайте автоматизируем перенаправление с помощью следующего shell-скрипта:
И отметим его исполняемым:
А затем внесем изменения в командную строку ядра:
И повторим:
Сеть работает стабильно!
Чтобы вам было проще все это проверить, я собрала Dockerfile, который автоматизирует большинство описанных шагов и должен обеспечить вам рабочую конфигурацию. Еще у меня есть готовая конфигурация ядра, в которой есть все, что описано в посте. Но важно понимать, что тут я изложила только минимальную настройку.
Я надеюсь, что этот пост помог вам понять, как поднять гостевое ядро. Получился какой-то монстр, но задумывалась публикация в качестве всеобъемлющего руководства на тему сборки, установки и настройки User Mode в Linux под современными версиями операционных систем этого семейства. Последующие действия должны включать в себя установку сервисов и прочего программного обеспечения уже внутри гостевой системы. Так как образы контейнеров Docker — это просто распиаренные tar-архивы, вы должны быть в состоянии извлечь образ через
Отдельное спасибо Rkeene с #lobsters на Freenode. Без его помощи в отладке Slirp я бы не зашла так далеко. Я понятия не имею, как его система Slackware корректно работает со slirp, но мои системы Ubuntu и Alpine не приняли slirp и предложенный мне Rkeene бинарник. Но мне достаточно и того, что у меня работает хоть что-то.
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 в User Mode:
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 для драйверов в User Mode. Планирую сообщить об этом в апстрим после того, как окончательно разберусь в проблеме.
Установка зависимостей на хосте
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 Гб пространства на жестком диске, поэтому убедитесь, что оно у вас есть.
После перейдите на 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]
', вы сможете заняться настройкой с помощью клавиш Пробел или Ввод. Навигация по окну, как обычно, стрелками клавиатуры «вверх» и «вниз», а выделение элементов — «влево» или «вправо». Указатель вида ---> означает, что вы находитесь в подменю, вход в которое осуществляется клавишей Ввод. Выход из него, очевидно, через '
[Exit]
'.Включите следующие параметры в '
[Select]
' и убедитесь, что рядом с ними есть символ '[*]':UML-specific Options:
- Host filesystem
Networking support (enable this to get the submenu to show up):
- Networking options:
- TCP/IP Networking
UML Network devices:
- Virtual network device
- SLiRP transport
Все, из этого окна можно выходить, последовательно выбирая '
[Exit]
'. Только убедитесь, что в конце вам предложит сохранить конфигурацию и выберете '[Yes]
'.Я рекомендую вам поиграться с параметрами сборки ядра после прочтения этого поста. Благодаря этим экспериментами вы сможете многое почерпнуть в плане понимания работы низкоуровневых механик ядра и влияния различных флагов на его сборку.
Сборка ядра
Ядро Linux — это большая программа, занимающаяся множеством вещей. Даже при такой минимальной конфигурации на старом оборудовании его сборка может занять достаточно времени. Поэтому собирайте ядро с помощью следующей команды:
make ARCH=um -j$(nproc)
Зачем? Эта команда скажет нашему сборщику использовать все доступные ядра и потоки процессора в процессе сборки. Команда
$(nproc)
в конце Build подставляет вывод команды 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
available at http://user-mode-linux.sourceforge.net/
--showconfig
Prints the config file that this UML binary was generated from.
iomem=<name>,<file>
Configure <file> as an IO memory region named <name>.
mem=<Amount of desired ram>
This controls how much "physical" memory the kernel allocates
for the system. The size is specified as a number followed by
one of 'k', 'K', 'm', 'M', which have the obvious meanings.
This is not related to the amount of memory in the host. It can
be more, and the excess, if it's ever used, will just be swapped out.
Example: mem=64M
--help
Prints this message.
debug
this flag is not needed to run gdb on UML in skas mode
root=<file containing the root fs>
This is actually used by the generic kernel in exactly the same
way as in any other kernel. If you configure a number of block
devices and want to boot off something other than ubd0, you
would use something like:
root=/dev/ubd5
--version
Prints the version number of the kernel.
umid=<name>
This is used to assign a unique identity to this UML machine and
is used for naming the pid file and management console socket.
con[0-9]*=<channel description>
Attach a console or serial line to a host channel. See
http://user-mode-linux.sourceforge.net/old/input.html for a complete
description of this switch.
eth[0-9]+=<transport>,<options>
Configure a network device.
aio=2.4
This is used to force UML to use 2.4-style AIO even when 2.6 AIO is
available. 2.4 AIO is a single thread that handles one request at a
time, synchronously. 2.6 AIO is a thread which uses the 2.6 AIO
interface to handle an arbitrary number of pending requests. 2.6 AIO
is not available in tt mode, on 2.4 hosts, or when UML is built with
/usr/include/linux/aio_abi.h not available. Many distributions don't
include aio_abi.h, so you will need to copy it from a kernel tree to
your /usr/include/linux in order to build an AIO-capable UML
nosysemu
Turns off syscall emulation patch for ptrace (SYSEMU).
SYSEMU is a performance-patch introduced by Laurent Vivier. It changes
behaviour of ptrace() and helps reduce host context switch rates.
To make it work, you need a kernel patch for your host, too.
See http://perso.wanadoo.fr/laurent.vivier/UML/ for further
information.
uml_dir=<directory>
The location to place the pid and umid files.
quiet
Turns off information messages during boot.
hostfs=<root dir>,<flags>,...
This is used to set hostfs parameters. The root directory argument
is used to confine all hostfs mounts to within the specified directory
tree on the host. If this isn't specified, then a user inside UML can
mount anything on the host that's accessible to the user that's running
it.
The only flag currently supported is 'append', which specifies that all
files opened by hostfs will be opened in append mode.
Это полотнище освещает основные параметры запуска. Давайте запустим ядро с минимальным необходимым набором опций:
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
-процесс.
Запустите эту команду, и вы должны получить что-то вроде следующего:
Еще одна простыня
Core dump limits :
soft - 0
hard - NONE
Checking that ptrace can change system call numbers...OK
Checking syscall emulation patch for ptrace...OK
Checking advanced syscall emulation patch for ptrace...OK
Checking environment variables for a tempdir...none found
Checking if /dev/shm is on tmpfs...OK
Checking PROT_EXEC mmap in /dev/shm...OK
Adding 32137216 bytes to physical memory to account for exec-shield gap
Linux version 5.1.16 (cadey@kahless) (gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) #30 Sun Jul 7 18:57:19 UTC 2019
Built 1 zonelists, mobility grouping on. Total pages: 23898
Kernel command line: root=/dev/root rootflags=/home/cadey/dl/uml/alpine rootfstype=hostfs rw mem=64M init=/bin/sh
Dentry cache hash table entries: 16384 (order: 5, 131072 bytes)
Inode-cache hash table entries: 8192 (order: 4, 65536 bytes)
Memory: 59584K/96920K available (2692K kernel code, 708K rwdata, 588K rodata, 104K init, 244K bss, 37336K reserved, 0K cma-reserved)
SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
NR_IRQS: 15
clocksource: timer: mask: 0xffffffffffffffff max_cycles: 0x1cd42e205, max_idle_ns: 881590404426 ns
Calibrating delay loop... 7479.29 BogoMIPS (lpj=37396480)
pid_max: default: 32768 minimum: 301
Mount-cache hash table entries: 512 (order: 0, 4096 bytes)
Mountpoint-cache hash table entries: 512 (order: 0, 4096 bytes)
Checking that host ptys support output SIGIO...Yes
Checking that host ptys support SIGIO on close...No, enabling workaround
devtmpfs: initialized
random: get_random_bytes called from setup_net+0x48/0x1e0 with crng_init=0
Using 2.6 host AIO
clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns
futex hash table entries: 256 (order: 0, 6144 bytes)
NET: Registered protocol family 16
clocksource: Switched to clocksource timer
NET: Registered protocol family 2
tcp_listen_portaddr_hash hash table entries: 256 (order: 0, 4096 bytes)
TCP established hash table entries: 1024 (order: 1, 8192 bytes)
TCP bind hash table entries: 1024 (order: 1, 8192 bytes)
TCP: Hash tables configured (established 1024 bind 1024)
UDP hash table entries: 256 (order: 1, 8192 bytes)
UDP-Lite hash table entries: 256 (order: 1, 8192 bytes)
NET: Registered protocol family 1
console [stderr0] disabled
mconsole (version 2) initialized on /home/cadey/.uml/tEwIjm/mconsole
Checking host MADV_REMOVE support...OK
workingset: timestamp_bits=62 max_order=14 bucket_order=0
Block layer SCSI generic (bsg) driver version 0.4 loaded (major 254)
io scheduler noop registered (default)
io scheduler bfq registered
loop: module loaded
NET: Registered protocol family 17
Initialized stdio console driver
Using a channel type which is configured out of UML
setup_one_line failed for device 1 : Configuration failed
Using a channel type which is configured out of UML
setup_one_line failed for device 2 : Configuration failed
Using a channel type which is configured out of UML
setup_one_line failed for device 3 : Configuration failed
Using a channel type which is configured out of UML
setup_one_line failed for device 4 : Configuration failed
Using a channel type which is configured out of UML
setup_one_line failed for device 5 : Configuration failed
Using a channel type which is configured out of UML
setup_one_line failed for device 6 : Configuration failed
Using a channel type which is configured out of UML
setup_one_line failed for device 7 : Configuration failed
Using a channel type which is configured out of UML
setup_one_line failed for device 8 : Configuration failed
Using a channel type which is configured out of UML
setup_one_line failed for device 9 : Configuration failed
Using a channel type which is configured out of UML
setup_one_line failed for device 10 : Configuration failed
Using a channel type which is configured out of UML
setup_one_line failed for device 11 : Configuration failed
Using a channel type which is configured out of UML
setup_one_line failed for device 12 : Configuration failed
Using a channel type which is configured out of UML
setup_one_line failed for device 13 : Configuration failed
Using a channel type which is configured out of UML
setup_one_line failed for device 14 : Configuration failed
Using a channel type which is configured out of UML
setup_one_line failed for device 15 : Configuration failed
Console initialized on /dev/tty0
console [tty0] enabled
console [mc-1] enabled
Failed to initialize ubd device 0 :Couldn't determine size of device's file
VFS: Mounted root (hostfs filesystem) on device 0:11.
devtmpfs: mounted
This architecture does not have kernel memory protection.
Run /bin/sh as init process
/bin/sh: can't access tty; job control turned off
random: fast init done
/ #
Манипуляции выше дадут нам гостевую систему на минималках, без таких вещей, как
/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 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 ...
fish: “/usr/bin/slirp” terminated by signal SIGSEGV (Address boundary error)
Ох, божечки. Давайте установим отладчик для 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 <http://gnu.org/licenses/gpl.html>
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:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
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.
Ошибка бьется у нас в этой строке. Давайте посмотрим на stacktrace, может там нам что-нибудь поможет:
(gdb) bt full
#0 ip_slowtimo () at ip_input.c:457
fp = 0x55784a40
#1 0x000055555556a57c in main_loop () at ./main.c:980
so = <optimized out>
so_next = <optimized out>
timeout = {tv_sec = 0, tv_usec = 0}
ret = 0
nfds = 0
ttyp = <optimized out>
ttyp2 = <optimized out>
best_time = <optimized out>
tmp_time = <optimized out>
#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
<enter>
#define inline /**/
<escape>
: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 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]
SLiRP Ready ...
Не падает — так что должно сработать! Давайте подсадим этот бинарник в
~/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
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: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
/ #
Видимо, ядро не может связаться с драйвером сетевухи. Попытка вкомпилить firmware в ядро ситуацию, к сожалению, не исправила. На момент публикации именно в такой конфигурации решения пока найти не удалось. На более простых конфигах (например, в 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
Сеть работает стабильно!
Докер-файл
Чтобы вам было проще все это проверить, я собрала Dockerfile, который автоматизирует большинство описанных шагов и должен обеспечить вам рабочую конфигурацию. Еще у меня есть готовая конфигурация ядра, в которой есть все, что описано в посте. Но важно понимать, что тут я изложила только минимальную настройку.
Я надеюсь, что этот пост помог вам понять, как поднять гостевое ядро. Получился какой-то монстр, но задумывалась публикация в качестве всеобъемлющего руководства на тему сборки, установки и настройки User Mode в Linux под современными версиями операционных систем этого семейства. Последующие действия должны включать в себя установку сервисов и прочего программного обеспечения уже внутри гостевой системы. Так как образы контейнеров Docker — это просто распиаренные tar-архивы, вы должны быть в состоянии извлечь образ через
docker export
, а затем определить путь его установки в корне файловой системы гостевого ядра. Ну, а затем выполните shell-скрипт. Отдельное спасибо Rkeene с #lobsters на Freenode. Без его помощи в отладке Slirp я бы не зашла так далеко. Я понятия не имею, как его система Slackware корректно работает со slirp, но мои системы Ubuntu и Alpine не приняли slirp и предложенный мне Rkeene бинарник. Но мне достаточно и того, что у меня работает хоть что-то.