These are devices exposed in Ironclad regardless of target system when present, with standardized interfaces.
/dev/console wraps architecture-specific debug output channels for use
with file operations. For x86-based targets, this is COM1, for other targets,
this may be UART.
If the target implements reading from the debug channels, read will be supported as well. If not implemented, the device will be read-only.
The kernel also uses the debug channels for output, so keep in mind the contentions that can cause. If you are doing a lot of spaced writes, do not be surprised if the kernel pops in the middle! In the other hand, the kernel does not read from the debug channels.
The device random is equivalent to the one featured in other UNIX-like
kernels, and can be read to get a stream of cryptographically secure
pseudo-random bytes. It is implemented using a Fortuna-like PRNG, and does
not block when waiting for new entropy.
random allows for writing as well, which will take random data for
mixing into the kernel’s entropy pools.
Ironclad does not go out of its way to feed user-accessible discrete randomness
sources to the kernel’s entropy pools, like a hardware RNG, or x86’s
rdrand. It is highly recommended to use userland daemons to periodically
feed the kernel entropy with these kind of sources by writing to random.
This can also be used to implement seed files, for example:
# On boot, maybe as part of the init system. cat seedfile.bin > /dev/random # On poweroff. dd if=/dev/random of=seedfile.bin bs=4M count=1 iflag=fullblock
/dev/urandom does the same as /dev/random, and is only provided
as a virtual alias for compatibility.
getentropy is provided as well for avoiding the file interface when
interfacing with random, as that may avoid certain kinds of DoS
attacks related to opened file limits.
null returns EOF whenever read, and all the write operations are
discarded.
zero returns exclusively a stream of zeros when read, and all write
operations are discarded.
i6300esb is a hardware watchdog featured in a lot of intel hardware, it
can be reset by using write and can be configured using
ioctl like:
WDOG_START = 1 // Start the count. WDOG_STOP = 2 // Stop the count. WDOG_HEARTBEAT = 3 // Reset and set a new heartbeat period in seconds. ioctl(wdog, WDOG_START, ignored); // Enable 2:1 scaling. ioctl(wdog, WDOG_STOP, ignored); // Enable 1:1 scaling. ioctl(wdog, WDOG_HEARTBEAT, pointer_to_uint32_t);
There is no default heartbeat count, so be sure to configure it if you do not want mayhem. Access to reset and configuration can be restricted by using MAC.
While this piece of hardware allows for hooking up interrupts and reboot separately when the timer expires, Ironclad right now will only reboot when the timer expires.
The devices starting by sata represent several SATA AHCI block devices.
For now only SATA drives are supported, support for ATAPI is not present.
These SATA drives have internal caching at the driver level, so they must be
sync’d for data integrity when wanting to ensure data coherency.
The devices starting by nvme represent NVMe block devices, namespaces
are named using n followed by the namespace number.
NVMe drives have internal caching at the driver level, so they must be
sync’d for data integrity when wanting to ensure data coherency.
These devices are provided only by platforms implementing power and sleep
buttons. Programs can poll on them for read permission. When the
corresponding device reports being able to read, it shall be interpreted as a
oneshot event representing a pressing of the corresponding button.
These devices represent VirtIO block devices, usually provided by hypervisors
like QEMU. They support read/write operations, and must be sync’d for
data integrity when wanting to ensure data coherency. Ironclad only supports
modern PCI transport, and does not bother supporting legacy or transitional
devices.
These devices are named virtio-blk followed by a number, starting at 1.
To launch qemu with a virtio block device, use something like:
qemu-system-x86_64 -device virtio-blk-pci,drive=boot,disable-legacy=on -drive id=boot,file=<disk contents>,format=raw,if=none
These devices provide a stream of random bytes generated by the host. They are
named virtio-rng followed by a number, starting at 1. They can be used
like any other character device, but do not support writing. Reading from them
is a blocking operation.
They can be useful for feeding the kernel entropy pool on virtualized environments.
To launch qemu with a VirtIO RNG device, use something like:
qemu-system-x86_64 -device virtio-rng-pci,disable-legacy=on
These devices allow paravirtualized networking. The driver exposes them as
character devices named virtio-net, followed by a number. Writing sends
packets, and reading performs a blocking receive.
To launch qemu with a VirtIO Network device, use something like:
qemu-system-x86_64 -netdev user,id=net0 -device virtio-net,disable-legacy=on,netdev=net0
In order to capture packets in a pcap file for analyzing it with Wireshark, append this to the command line:
-object filter-dump,id=pcap,file=capture.pcap,netdev=net0
A sample program demonstrating how to send and receive ARP packets:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <unistd.h> #include <fcntl.h> #include <arpa/inet.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #define ETH_P_ARP 0x0806 #define ETH_HDR_LEN 14 #define ARP_PKT_LEN 28 int main() { const char *dev = "/dev/virtio-net0"; int fd = open(dev, O_RDWR); if (fd < 0) { perror("open"); return 1; } // Hardcoded MAC and IP addresses uint8_t src_mac[6] = {0x52, 0x54, 0x00, 0x12, 0x34, 0x56}; uint8_t dst_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; // Broadcast struct in_addr src_ip, target_ip; inet_pton(AF_INET, "10.0.2.15", &src_ip); inet_pton(AF_INET, "10.0.2.2", &target_ip); // Prepare Ethernet frame uint8_t frame[ETH_HDR_LEN + ARP_PKT_LEN]; memset(frame, 0, sizeof(frame)); // Ethernet header memcpy(frame, dst_mac, 6); // Destination MAC memcpy(frame + 6, src_mac, 6); // Source MAC frame[12] = 0x08; frame[13] = 0x06; // Ethertype: ARP // ARP payload uint8_t *arp = frame + ETH_HDR_LEN; arp[0] = 0x00; arp[1] = 0x01; // Hardware type: Ethernet arp[2] = 0x08; arp[3] = 0x00; // Protocol type: IPv4 arp[4] = 6; // Hardware size arp[5] = 4; // Protocol size arp[6] = 0x00; arp[7] = 0x01; // Opcode: request memcpy(arp + 8, src_mac, 6); // Sender MAC memcpy(arp + 14, &src_ip.s_addr, 4); // Sender IP memset(arp + 18, 0x00, 6); // Target MAC (unknown) memcpy(arp + 24, &target_ip.s_addr, 4); // Target IP // Send ARP request ssize_t w = write(fd, frame, sizeof(frame)); if (w != sizeof(frame)) { perror("write"); close(fd); return 1; } printf("ARP request sent: Who has 10.0.2.2? Tell 10.0.2.15\n"); // Blocking read for ARP reply uint8_t buf[1526]; while (1) { ssize_t r = read(fd, buf, sizeof(buf)); if (r < ETH_HDR_LEN + ARP_PKT_LEN) continue; // Check ethertype == ARP if (buf[12] != 0x08 || buf[13] != 0x06) continue; uint8_t *r_arp = buf + ETH_HDR_LEN; if (r_arp[6] != 0x00 || r_arp[7] != 0x02) continue; // Opcode != reply // Check that target IP is our IP (10.0.2.15) struct in_addr target_ip_in_reply; memcpy(&target_ip_in_reply.s_addr, r_arp + 24, 4); if (target_ip_in_reply.s_addr != src_ip.s_addr) continue; // Check that sender IP is 10.0.2.2 struct in_addr sender_ip; memcpy(&sender_ip.s_addr, r_arp + 14, 4); if (sender_ip.s_addr != target_ip.s_addr) continue; // Print sender MAC (owner of 10.0.2.2) uint8_t *sender_mac = r_arp + 8; printf("ARP reply received: 10.0.2.2 is at %02x:%02x:%02x:%02x:%02x:%02x\n", sender_mac[0], sender_mac[1], sender_mac[2], sender_mac[3], sender_mac[4], sender_mac[5]); break; } close(fd); return 0; }