company_banner

Как начать использовать User Mode в Linux

Автор оригинала: Christine Dodrill
  • Перевод
Вступление от переводчика: На фоне массового входа в нашу жизнь различного рода контейнеров может быть довольно интересно и полезно узнать, с каких технологий это всё начиналось когда-то. Некоторые из них можно с пользой применять и по сей день, но не все о таких способах помнят (или знают, если не застали во время их бурного развития). Одной из таких технологий является User Mode Linux. Автор оригинала изрядно покопалась, разбираясь, что из старых наработок ещё работает, а что уже не очень, и собрала нечто вроде пошаговой инструкции о том, как самому себе завести доморощенный UML в 2к19. И да, мы пригласили на Хабр автора оригинального поста Cadey, так что если есть вопросы — задавайте на английском в комментариях.

image

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 бинарник. Но мне достаточно и того, что у меня работает хоть что-то.
ITSumma
286,40
Собираем безумных людей и вместе спасаем интернет
Поделиться публикацией

Комментарии 10

    +5

    Читал с большим интересом. Ожидал, что будет использование veth'а или что-то подобное. slirp не выглядит как полноценное решение.

      +3
      Slirp автор выбрала, я так понимаю, именно с упором на то, чтобы можно было всё запускать исключительно от непривилегированного пользователя, включая сеть.

      Если рутом махать, то, конечно, есть более широкий набор решений — ссылку в примечании в тексте добавили на этот счет)
        +2

        Вот это как раз и есть интересный вопрос. В linux почему-то (до сих пор) нельзя взять и отдать сетевой интерфейс на растерзание непривилегированной программе.

          +9

          Half the problem comes from an arbitrary restriction I put on myself. I wanted this to work from a completely unprivileged Docker container. Normally Docker doesn't give guest containers things like CAP_NET_ADMIN, which is needed to do things with TUN/TAP devices. I wanted to make the container image so that all you needed to do was:


          docker run --rm -it xena/docker-uml

          and then you'd suddenly have an entirely working user mode linux system right there. I mainly want to see how far the rabbit hole goes from a completely user level. TUN/TAP devices require CAP_NET_ADMIN, so they are not allowed in this case. Thus I chose Slirp.

            +2

            Да, так я про это и говорю. CAP_NET_ADMIN — это не "отдать интерфейс на растерзание", а "отдать сеть всего сервера на растерзание". Вот если бы можно было дать CAP_NET_ADMIN для одного-единственного сетевого интерфейса (например, veth'а), или даже создать сетевой интерфейс в непривилегированном режиме.

              +2

              I'd absolutely love if that was the case. It's a shame that we live in a world where we can't do that.

        +2
        Этот же вопрос можно продублировать самому автору Cadey. Правда в Монреале пока 4 утра, так что ответы от нее будут после обеда.
        +11
        Hi, author of the post here. Sorry I'm not the best with Russian, so I'll be using Google Translate to understand comments here and I'll answer questions in English.
          0

          Hi. Hopefully this won't sound like an advertisement, but the recently-released DeepL machine translation works way better for ru<->eng than Google Translate in my experience. Just in case.

            0

            I wasn't aware of that, thanks!

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое