Accessing the Device From User Space
This is the final step of the guided part of this exercise. We will now develop a simple user space application that accesses the virtual device through the driver we just implemented.
Connecting via SSH from the Host to the VM
In this part you may need to edit files within the VM, and possibly transfer files between the host and the VM. You will notice that Qemu's virtual serial output (the console you get in the terminal after starting the VM) is not very stable when you type long commands (> 1 line of terminal), and that text editors also struggle to display things correctly in the VM. To get access to a stable console, it is better to rely on an SSH connection from the host to the VM.
With the simple virtual network we are using for that exercise (the -nic user
option of Qemu), the host and the guest don't see each other directly, but we can use the following trick: Qemu's networking can be used to forward the SSH port of the VM on a given port p on the host.
Once this is done, by connecting via SSH from the host locally on p, we end up in the VM.
To forward the VM's SSH port (22
) to a port on the host, i.e. 1022
, change the -nic
option of Qemu in your VM launch script of Qemu to the following:
-nic user,hostfwd=tcp::1022-:22
Launch the VM, and wait for it to boot. Then, from the host, connect via SSH to the local port 1022 in a new terminal:
ssh root@localhost -p 1022
root@localhost's password:
Welcome to Alpine!
You are now in the VM.
You can also use scp
to transfer files between the host and the VM, and vice versa.
These transfers need to be initiated from the host.
For example to transfer a file from the host to the VM:
scp -P 1022 /path/to/local-file-on-the-host.txt root@localhost:/path/to/destination/on/the/vm
And to transfer a file from the VM to the host:
scp -P 1022 root@localhost:/path/to/source-file-on-the-vm.txt /path/to/destination/on/the/host
Note that for
ssh
we indicate the port withp
, while forscp
it is done withP
, which is not particularly intuitive.
Creating the Virtual File for the Device
Before we can write the user space app that will connect to the device through the driver, we need to create the virtual file /dev/my_rng_driver
mentioned in the previous step.
To do so, type the following command within the VM:
mknod /dev/my_rng_driver c 250 0
The major number, here 250, must match the one you defined within the driver in the initialisation function.
After invoking mknod
the virtual file should be present in /dev
:
ls -l /dev/my_rng_driver
crw-r--r-- 1 root root 250, 0 Dec 20 22:32 /dev/my_rng_driver
You will need to repeat that operation each time the VM reboots.
To avoid doing so, you can configure Alpine to automatically create the virtual file each time the VM boots by creating a file (in the VM) /etc/init.d/init-my-rng-virtual-file
and placing the following in it:
#!/sbin/openrc-run
mknod /dev/my_rng_driver c 250 0
Then giving that file execution permissions:
chmod +x /etc/init.d/init-my-rng-virtual-file
Installing a C Toolchain and a Text Editor in the VM
We will next write the user space application.
You can either write it on the host and transfer the source file to the VM, or write it directly within the VM
In both cases the application's source file will need to be compiled in the VM.
You can install the text editors vim
and nano
, as well as the build toolchain (C compiler, etc.), with the Alpine package manager.
To do so, run the following command inside the VM:
apk add build-base vim nano
You can now use vim
or nano
to edit files, and use gcc
to compile C programs in the VM.
Writing the User Space Application
The source code of the user space application follows.
We start by including a few headers for printing to the standard output, accessing files, and performing ioctl
commands.
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
Next we have two constants that are the ioctl
numbers that were allocated for the 2 functions offered by the driver.
To find them look in the VM's kernel log.
#define RAND_IOCTL 0x80047101
#define SEED_IOCTL 0x40047101
Finally, we have the main function that contains our test code:
int main() {
int fd = open("/dev/my_rng_driver", O_RDWR);
if (fd < 0) {
perror("Failed to open the device file");
return -1;
}
unsigned int seed = 0x0;
unsigned int random_number = 0;
for(int i=0; i<2; i++) {
// seed the generator
if(ioctl(fd, SEED_IOCTL, &seed)) {
perror("ioctl seed");
return -1;
}
// get 5 random numbers
for (int j=0; j<5; j++) {
if(ioctl(fd, RAND_IOCTL, &random_number)) {
perror("ioctl rand");
return -1;
}
printf("Round %d number %d: %u\n", i, j, random_number);
}
}
close(fd);
return 0;
}
This code starts by opening the virtual file representing the driver, /dev/my_rng_driver
.
It then follows similar steps to our in-kernel test we ran earlier: we seed the RNG, and generate 5 random numbers.
We do that twice in a row to confirm that with the same seed, the device will return the same sequence of random numbers.
Notice how ioctl
is called with as parameter:
- The virtual file descriptor
fd
- The
ioctl
code we want to invoke (RAND_IOCTL
orSEED_IOCTL
) - The address of a variable that will be filled with the random number generated (for
RAND_IOCTL
), or the address of a variable holding the seed we want to use (forSEED_IOCTL
).
You can compile that code within the VM, assuming you write it in a file named my-app.c
as follows:
gcc my-app.c -o my-app
When launching the program, you should see a series of 2 similar random number sequences:
./my-app
Round 0 number 0: 1804289383
Round 0 number 1: 846930886
Round 0 number 2: 1681692777
Round 0 number 3: 1714636915
Round 0 number 4: 1957747793
Round 1 number 0: 1804289383
Round 1 number 1: 846930886
Round 1 number 2: 1681692777
Round 1 number 3: 1714636915
Round 1 number 4: 1957747793
That's it! We have reached the end of the guided part of this exercise. Now the next step is to enhance the device/driver. There are various ways to achieve that, and it is up to you to choose an avenue of improvement. A few suggestions are given in the next and last step of this guide.