forked from cadey/xesite
blog: user mode linux post (#62)
* blog: first draft of user mode linux post * blog/user-mode-linux: special thanks
This commit is contained in:
parent
68ca55eeed
commit
55641a9dad
|
@ -0,0 +1,808 @@
|
|||
---
|
||||
title: How to Use User Mode Linux
|
||||
date: 2019-07-07
|
||||
---
|
||||
|
||||
# How to Use User Mode Linux
|
||||
|
||||
[User Mode Linux](http://user-mode-linux.sourceforge.net) is a port of the
|
||||
[Linux kernel](https://www.kernel.org) to itself. This allows you to run a
|
||||
full blown Linux kernel as a normal userspace process. This is used by kernel
|
||||
developers for testing drivers, but is also useful as a generic isolation layer
|
||||
similar to virtual machines. It provides slightly more isolation than [Docker](https://www.docker.com),
|
||||
but slightly less isolation than a full-blown virtual machine like KVM or
|
||||
VirtualBox.
|
||||
|
||||
In general, this may sound like a weird and hard to integrate tool, but it does
|
||||
have its uses. It is an entire Linux kernel running as a normal user. This
|
||||
allows you to run potentially untrusted code without affecting the host machine.
|
||||
It also allows you to test experimental system configuration changes without
|
||||
having to reboot or take its services down.
|
||||
|
||||
Also, because this kernel and its processes are isolated from the host machine,
|
||||
this means that processes running inside a user mode Linux kernel will _not_ be
|
||||
visible to the host machine. This is unlike a Docker container, where processes
|
||||
in those containers are visible to the host. See this (snipped) pstree output
|
||||
from one of my servers:
|
||||
|
||||
```
|
||||
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}]
|
||||
```
|
||||
|
||||
Compare it to the user mode Linux pstree output:
|
||||
|
||||
```
|
||||
linux─┬─5*[linux]
|
||||
└─slirp
|
||||
```
|
||||
|
||||
With a Docker container, I can see the names of the processes being run in the
|
||||
guest from the host. With a user mode Linux kernel, I cannot do this. This means
|
||||
that monitoring tools that function using [Linux's auditing subsystem](https://www.digitalocean.com/community/tutorials/how-to-use-the-linux-auditing-system-on-centos-7)
|
||||
_cannot_ monitor processes running inside the guest. This could be a two-edged
|
||||
sword in some edge scenarios.
|
||||
|
||||
This post represents a lot of research and brute-force attempts at trying to do
|
||||
this. I have had to assemble things together using old resources, reading kernel
|
||||
source code, intense debugging of code that was last released when I was in
|
||||
elementary school, tracking down a Heroku buildpack with a pre-built binary for
|
||||
a tool I need and other hackery that made people in IRC call me magic. I hope
|
||||
that this post will function as reliable documentation for doing this with a
|
||||
modern kernel and operating system.
|
||||
|
||||
## Setup
|
||||
|
||||
Setting up user mode Linux is done in a few steps:
|
||||
|
||||
- Installing host dependencies
|
||||
- Downloading Linux
|
||||
- Configuring Linux
|
||||
- Building the kernel
|
||||
- Installing the binary
|
||||
- Setting up the guest filesystem
|
||||
- Creating the kernel command line
|
||||
- Setting up networking for the guest
|
||||
- Running the guest kernel
|
||||
|
||||
I am assuming that you are wanting to do this on Ubuntu or another Debian-like
|
||||
system. I have tried to do this from Alpine (my distro of choice), but I have
|
||||
been unsuccessful as the Linux kernel seems to have glibc-isms hard-assumed in
|
||||
the user mode Linux drivers. I plan to report these to upstream when I have
|
||||
debugged them further.
|
||||
|
||||
### Installing Host Dependencies
|
||||
|
||||
Ubuntu requires at least the following packages installed to build the Linux
|
||||
kernel (assuming a completely fresh install):
|
||||
|
||||
- `build-essential`
|
||||
- `flex`
|
||||
- `bison`
|
||||
- `xz-utils`
|
||||
- `wget`
|
||||
- `ca-certificates`
|
||||
- `bc`
|
||||
- `linux-headers-4.15.0-47-generic` (though any kernel version will do)
|
||||
|
||||
You can install these with the following command (as root or running with sudo):
|
||||
|
||||
```
|
||||
apt-get -y install build-essential flex bison xz-utils wget ca-certificates bc \
|
||||
linux-headers-4.15.0-47-generic
|
||||
```
|
||||
|
||||
Additionally, running the menu configuration program for the Linux kernel will
|
||||
require installing `libncurses-dev`. Please make sure it's installed using the
|
||||
following command (as root or running with sudo):
|
||||
|
||||
```
|
||||
apt-get -y install libncurses-dev
|
||||
```
|
||||
|
||||
### Downloading the Kernel
|
||||
|
||||
Set up a location for the kernel to be downloaded and built. This will require
|
||||
approximately 1.3 gigabytes of space to run, so please make sure that there is
|
||||
at least this much space free.
|
||||
|
||||
Head to [kernel.org](https://www.kernel.org) and get the download URL of the
|
||||
latest stable kernel. As of the time of writing this post, this URL is the
|
||||
following:
|
||||
|
||||
```
|
||||
https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.1.16.tar.xz
|
||||
```
|
||||
|
||||
Download this file with `wget`:
|
||||
|
||||
```
|
||||
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.1.16.tar.xz
|
||||
```
|
||||
|
||||
And extract it with `tar`:
|
||||
|
||||
```
|
||||
tar xJf linux-5.1.16.tar.xz
|
||||
```
|
||||
|
||||
Now enter the directory created by the tarball extraction:
|
||||
|
||||
```
|
||||
cd linux-5.1.16
|
||||
```
|
||||
|
||||
### Configuring the Kernel
|
||||
|
||||
The kernel build system is a bunch of [Makefiles](https://en.wikipedia.org/wiki/Makefile)
|
||||
with a _lot_ of custom tools and scripts to automate builds. Open the interactive
|
||||
configuration program:
|
||||
|
||||
```
|
||||
make ARCH=um menuconfig
|
||||
```
|
||||
|
||||
It will build some things and then present you with a dialog interface. You can
|
||||
enable settings by pressing `Space` or `Enter` when `<Select>` is highlighted on
|
||||
the bottom of the screen. You can change which item is selected in the upper
|
||||
dialog with the and down arrow keys. You can change which item is highlighted on
|
||||
the bottom of the screen with the left and right arrow keys.
|
||||
|
||||
When there is a `--->` at the end of a feature name, that means it is a submenu.
|
||||
You can enter a submenu using the `Enter` key. If you enter a menu you can exit
|
||||
it with `<Exit>`.
|
||||
|
||||
Enable the following settings with `<Select>`, making sure there is a `[*]` next
|
||||
to them:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
Then exit back out to a shell by selecting `<Exit>` until there is a dialog
|
||||
asking you if you want to save your configuration. Select `<Yes>` and hit
|
||||
`Enter`.
|
||||
|
||||
I encourage you to play around with the build settings after reading through
|
||||
this post. You can learn a lot about Linux at a low level by changing flags and
|
||||
seeing how they affect the kernel at runtime.
|
||||
|
||||
### Building the Kernel
|
||||
|
||||
The Linux kernel is a large program with a lot of things going on. Even with
|
||||
this rather minimal configuration, it can take a while on older hardware. Build
|
||||
the kernel with the following command:
|
||||
|
||||
```
|
||||
make ARCH=um -j$(nproc)
|
||||
```
|
||||
|
||||
This will tell `make` to use all available CPU cores/hyperthreads to build the
|
||||
kernel. The `$(nproc)` at the end of the build command tells the shell to paste
|
||||
in the output of the `nproc` command (this command is part of `coreutils`, which
|
||||
is a default package in Ubuntu).
|
||||
|
||||
After a while, the kernel will be built to `./linux`.
|
||||
|
||||
### Installing the Binary
|
||||
|
||||
Because user mode Linux builds a normal binary, you can install it like you would
|
||||
any other command line tool. Here's the configuration I use:
|
||||
|
||||
```
|
||||
mkdir -p ~/bin
|
||||
cp linux ~/bin/linux
|
||||
```
|
||||
|
||||
If you want, ensure that `~/bin` is in your `$PATH`:
|
||||
|
||||
```
|
||||
export PATH=$PATH:$HOME/bin
|
||||
```
|
||||
|
||||
### Setting up the Guest Filesystem
|
||||
|
||||
Create a home for the guest filesystem:
|
||||
|
||||
```
|
||||
mkdir -p $HOME/prefix/uml-demo
|
||||
cd $HOME/prefix
|
||||
```
|
||||
|
||||
Open [alpinelinux.org](https://alpinelinux.org). Click on [Downloads](https://alpinelinux.org/downloads).
|
||||
Scroll down to where it lists the `MINI ROOT FILESYSTEM`. Right-click on the
|
||||
`x86_64` link and copy it. As of the time of writing this post, the latest URL
|
||||
for this is:
|
||||
|
||||
```
|
||||
http://dl-cdn.alpinelinux.org/alpine/v3.10/releases/x86_64/alpine-minirootfs-3.10.0-x86_64.tar.gz
|
||||
```
|
||||
|
||||
Download this tarball to your computer:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
Now enter the guest filesystem folder and extract the tarball:
|
||||
|
||||
```
|
||||
cd uml-demo
|
||||
tar xf ../alpine-rootfs.tgz
|
||||
```
|
||||
|
||||
This will create a very minimal filesystem stub. Because of how this is being
|
||||
run, it will be difficult to install binary packages from Alpine's package
|
||||
manager `apk`, but this should be good enough to work as a proof of concept.
|
||||
|
||||
The tool [`tini`](https://github.com/krallin/tini) will be needed in order to
|
||||
prevent the guest kernel from having its memory used up by [zombie processes](https://en.wikipedia.org/wiki/Zombie_process).
|
||||
|
||||
Install it by doing the following:
|
||||
|
||||
```
|
||||
wget -O tini https://github.com/krallin/tini/releases/download/v0.18.0/tini-static
|
||||
chmod +x tini
|
||||
```
|
||||
|
||||
### Creating the Kernel Command Line
|
||||
|
||||
The Linux kernel has command line arguments like most other programs. To view
|
||||
what command line options are compiled into the user mode kernel, run `--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.
|
||||
```
|
||||
|
||||
This is a lot of output, but it explains the options available in detail. Let's
|
||||
start up a kernel with a very minimal set of options:
|
||||
|
||||
```
|
||||
linux \
|
||||
root=/dev/root \
|
||||
rootfstype=hostfs \
|
||||
rootflags=$HOME/prefix/uml-demo \
|
||||
rw \
|
||||
mem=64M \
|
||||
init=/bin/sh
|
||||
```
|
||||
|
||||
This tells the guest kernel to do the following things:
|
||||
|
||||
- Assume the root filesystem is the pseudo-device `/dev/root`
|
||||
- Select [hostfs](http://user-mode-linux.sourceforge.net/hostfs.html) as the root filesystem driver
|
||||
- Mount the guest filesystem we have created as the root device
|
||||
- In read-write mode
|
||||
- Use only 64 megabytes of ram (you can get away with far less depending on what you are doing, but 64 MB seems to be a happy medium)
|
||||
- Have the kernel automatically start `/bin/sh` as the `init` process
|
||||
|
||||
Run this command, you should get something like the following output:
|
||||
|
||||
```
|
||||
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
|
||||
/ #
|
||||
```
|
||||
|
||||
This gives you a _very minimal_ system, without things like `/proc` mounted, or
|
||||
a hostname assigned. Try the following commands:
|
||||
|
||||
- `uname -av`
|
||||
- `cat /proc/self/pid`
|
||||
- `hostname`
|
||||
|
||||
To exit this system, type in `exit` or press Control-d. This will kill the shell,
|
||||
making the guest kernel panic:
|
||||
|
||||
```
|
||||
/ # exit
|
||||
Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000000
|
||||
fish: “./linux root=/dev/root rootflag…” terminated by signal SIGABRT (Abort)
|
||||
```
|
||||
|
||||
This kernel panic happens because the Linux kernel always assumes that its init
|
||||
process is running. Without this process running, the system cannot function
|
||||
anymore and exits. Because this is a user mode process, this results in the
|
||||
process sending itself `SIGABRT`, causing it to exit.
|
||||
|
||||
### Setting up Networking for the Guest
|
||||
|
||||
This is about where things get really screwy. Networking for a user mode Linux
|
||||
system is where the "user mode" facade starts to fall apart. Networking at the
|
||||
_system_ level is usually limited to _privileged_ execution modes, for very
|
||||
understandable reasons.
|
||||
|
||||
#### The slirp Adventure
|
||||
|
||||
However, there's an ancient and largely unmaintained tool called [slirp](https://en.wikipedia.org/wiki/Slirp)
|
||||
that user mode Linux can interface with. It acts as a user-level TCP/IP stack
|
||||
and does not rely on any elevated permissions to run. This tool was first
|
||||
released in _1995_, and its last release was made in _2006_. This tool is old
|
||||
enough that compilers have changed so much in the meantime that the software
|
||||
has effectively [rotten](https://en.wikipedia.org/wiki/Software_rot).
|
||||
|
||||
So, let's install slirp from the Ubuntu repositories and test running it:
|
||||
|
||||
```
|
||||
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)
|
||||
```
|
||||
|
||||
Oh dear. Let's [install the debug symbols](https://wiki.ubuntu.com/Debug%20Symbol%20Packages)
|
||||
for slirp and see if we can tell what's going on:
|
||||
|
||||
```
|
||||
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.
|
||||
```
|
||||
|
||||
It fails at [this line](https://github.com/Pradeo/Slirp/blob/master/src/ip_input.c#L457).
|
||||
Let's see the detailed stacktrace to see if anything helps us:
|
||||
|
||||
```
|
||||
(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.
|
||||
```
|
||||
|
||||
So it's failing [in its main loop](https://github.com/Pradeo/Slirp/blob/master/src/main.c#L972)
|
||||
while it is trying to check if any timeouts occured. This is where I had to give
|
||||
up trying to debug this further. Let's see if building it from source works. I
|
||||
re-uploaded the tarball from [Sourceforge](http://slirp.sourceforge.net) because
|
||||
downloading tarballs from Sourceforge from the command line is a pain.
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
This spews warnings about undefined inline functions. This then fails to link
|
||||
the resulting binary. It appears that at some point between the release of this
|
||||
software and the current day, gcc stopped creating symbols for inline functions
|
||||
in intermediate compiled files. Let's try to globally replace the `inline`
|
||||
keyword with an empty comment to see if that works:
|
||||
|
||||
```
|
||||
vi slirp.h
|
||||
:6
|
||||
a
|
||||
<enter>
|
||||
#define inline /**/
|
||||
<escape>
|
||||
:wq
|
||||
make
|
||||
```
|
||||
|
||||
Nope. That doesn't work either. It continues to fail to find the symbols for
|
||||
those inline functions.
|
||||
|
||||
This is when I gave up. I started searching GitHub for [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks)
|
||||
that already had this implemented or done. My theory was that a Heroku
|
||||
buildpack would probably include the binaries I needed, so I searched for a bit
|
||||
and found [this buildpack](https://github.com/sleirsgoevy/heroku-buildpack-uml).
|
||||
I downloaded it and extracted `uml.tar.gz` and found the following files:
|
||||
|
||||
```
|
||||
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*
|
||||
```
|
||||
|
||||
That's a slirp binary! Does it work?
|
||||
|
||||
```
|
||||
./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 ...
|
||||
```
|
||||
|
||||
It's not immediately crashing, so I think it should be good! Let's copy this
|
||||
binary to `~/bin/slirp`:
|
||||
|
||||
```
|
||||
cp slirp ~/bin/slirp
|
||||
```
|
||||
|
||||
Just in case the person who created this buildpack takes it down, I have
|
||||
[mirrored it](https://git.xeserv.us/mirrors/heroku-buildpack-uml).
|
||||
|
||||
#### Configuring Networking
|
||||
|
||||
Now let's configure networking on our guest. [Adjust your kernel command line](http://user-mode-linux.sourceforge.net/old/networking.html):
|
||||
|
||||
```
|
||||
linux \
|
||||
root=/dev/root \
|
||||
rootfstype=hostfs \
|
||||
rootflags=$HOME/prefix/uml-demo \
|
||||
rw \
|
||||
mem=64M \
|
||||
eth0=slirp,,$HOME/bin/slirp \
|
||||
init=/bin/sh
|
||||
```
|
||||
|
||||
We should get that shell again. Let's enable networking:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
The first two commands set up `/proc` and `/sys`, which are required for
|
||||
`ifconfig` to function. The `ifconfig` command sets up the network interface
|
||||
to communicate with slirp. The route command sets the kernel routing table
|
||||
to force all traffic over the slirp tunnel. Let's test with a DNS query:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
That works!
|
||||
|
||||
Let's automate this with a shell script:
|
||||
|
||||
```
|
||||
#!/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
|
||||
```
|
||||
|
||||
and mark it executable:
|
||||
|
||||
```
|
||||
chmod +x init.sh
|
||||
```
|
||||
|
||||
and then change the kernel command line:
|
||||
|
||||
```
|
||||
linux \
|
||||
root=/dev/root \
|
||||
rootfstype=hostfs \
|
||||
rootflags=$HOME/prefix/uml-demo \
|
||||
rw \
|
||||
mem=64M \
|
||||
eth0=slirp,,$HOME/bin/slirp \
|
||||
init=/init.sh
|
||||
```
|
||||
|
||||
Then re-run it:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
And networking works reliably!
|
||||
|
||||
## Dockerfile
|
||||
|
||||
So that you can more easily test this, I have created a [Dockerfile](https://github.com/Xe/furry-happiness)
|
||||
that automates most of these steps and should result in a working setup. I have
|
||||
a [pre-made kernel configuration](https://github.com/Xe/furry-happiness/blob/master/uml.config)
|
||||
that should do everything outlined in this post, but this post outlines a more
|
||||
minimal setup.
|
||||
|
||||
---
|
||||
|
||||
I hope this post is able to help you understand how to do this. This became a bit
|
||||
of a monster, but this should be a comprehensive guide on how to build, install
|
||||
and configure user mode Linux for modern operating systems. Next steps from here
|
||||
should include installing services and other programs into the guest system.
|
||||
Since Docker container images are just glorified tarballs, you should be able to
|
||||
extract an image with `docker export` and then set the root filesystem location
|
||||
in the guest kernel to that location. Then run the command that the Dockerfile
|
||||
expects via a shell script.
|
||||
|
||||
Special thanks to rkeene of #lobsters on Freenode. Without his help with
|
||||
attempting to debug slirp, I wouldn't have gotten this far. I have no idea how
|
||||
his Slackware system works fine with slirp but my Ubuntu and Alpine systems
|
||||
don't, and why the binary he gave me also didn't work; but I got something
|
||||
working and that's good enough for me.
|
Loading…
Reference in New Issue